diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index d21ee5ea02..0e79851c0b 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -17,7 +17,7 @@ Assume positive intent and try to understand before being understood. Treat others as you would like to be treated. -This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.org](https://our.umbraco.org) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco. +This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.com](https://our.umbraco.com) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco. ## Open diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c257600769..6e421da5d7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -82,7 +82,7 @@ These wonderful volunteers will provide you with a first reply to your PR, revie You can get in touch with [the PR team](#the-pr-team) in multiple ways, we love open conversations and we are a friendly bunch. No question you have is stupid. Any questions you have usually helps out multiple people with the same question. Ask away: - If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward -- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.org/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely +- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely - We're also [active in the Gitter chatroom](https://gitter.im/umbraco/Umbraco-CMS) ## Code of Conduct diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md index 020346dc5e..a07539da6a 100644 --- a/.github/CONTRIBUTING_DETAILED.md +++ b/.github/CONTRIBUTING_DETAILED.md @@ -31,7 +31,7 @@ Before creating bug reports, please check [this list](#before-submitting-a-bug-r ##### Before Submitting A Bug Report - * Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.org/download/). We might have already fixed your particular problem. + * Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.com/download/). We might have already fixed your particular problem. * It also helps tremendously to check if the issue you're experiencing is present in **a clean install** of the Umbraco version you're currently using. Custom code can have side-effects that don't occur in a clean install. * **Use the Google**. Whatever you're experiencing, Google it plus "Umbraco" - usually you can get some pretty good hints from the search results, including open issues and further troubleshooting hints. * If you do find and existing issue has **and the issue is still open**, add a comment to the existing issue if you have additional information. If you have the same problem and no new info to add, just "star" the issue. @@ -65,13 +65,11 @@ Most of the suggestions in the [reporting bugs](#reporting-bugs) section also co Some additional hints that may be helpful: * **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Umbraco which the suggestion is related to. - * **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.org/projects/). + * **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.com/projects/). ### Your First Code Contribution -Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](http://issues.umbraco.org/issues/U4?q=%28project%3A+%7BU4%7D+Difficulty%3A+%7BVery+Easy%7D+%23Easy+%23Unresolved+Priority%3A+Normal+%23Major+%23Show-stopper+State%3A+-%7BIn+Progress%7D+sort+by%3A+votes+Affected+versions%3A+-6.*+Affected+versions%3A+-4.*%29+OR+%28tag%3A+%7BUp+For+Grabs%7D+%23Unresolved+%29). - -The issue list is sorted by total number of upvotes. While not perfect, number of upvotes is a reasonable proxy for impact a given change will have. +Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](https://issues.umbraco.org/issues?q=&project=U4&tagValue=upforgrabs&release=&issueType=&search=search) or on the [new issue tracker](https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aopen+is%3Aissue+label%3Acommunity%2Fup-for-grabs). ### Pull Requests @@ -80,7 +78,7 @@ The most successful pull requests usually look a like this: * Fill in the required template * Include screenshots and animated GIFs in your pull request whenever possible. * Unit tests, while optional are awesome, thank you! - * New code is commented with documentation from which [the reference documentation](https://our.umbraco.org/documentation/Reference/) is generated + * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated Again, these are guidelines, not strict requirements. @@ -116,8 +114,8 @@ There's two big areas that you should know about: To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation. - * [The AngularJS based backoffice files](https://our.umbraco.org/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`) - * [The rest](https://our.umbraco.org/apidocs/csharp/) + * [The AngularJS based backoffice files](https://our.umbraco.com/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`) + * [The rest](https://our.umbraco.com/apidocs/csharp/) ### What branch should I target for my contributions? diff --git a/.github/README.md b/.github/README.md index 83de27c859..cf29f4e527 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,9 +1,18 @@ -_You are browsing the Umbraco v8 branch._ +_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ _Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ +When is Umbraco 8 coming? +========================= +When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release: +1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors. +2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement +3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco + +Once a track is done, we start releasing previews where we ask people to test the features we believe are ready. While the testing is going on and we gather feedback, we move on to the next track. This doesn't mean that there hasn't already been work done in the area, but that a track focuses on finalizing, polishing and preparing the features for release. + Umbraco CMS =========== The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index 8cd792aa71..62b376b0e7 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -23,7 +23,7 @@ We recommend running the site with the Visual Studio since you'll be able to rem ### Making code changes -* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md)_ +* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_ * Any .NET changes you make you just need to compile * Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built. * When making js changes, you should have the chrome developer tools open to ensure that cache is disabled @@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there. -There's [a list of tasks for v8 that haven't been completed](http://issues.umbraco.org/issues/U4?q=Due+in+version%3A+8.0.0+%23Unresolved+). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. +There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. diff --git a/LICENSE.md b/LICENSE.md index c5560c3ce1..fa83dba963 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) # -Copyright (c) 2013 Umbraco +Copyright (c) 2013-present Umbraco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/build/build.ps1 b/build/build.ps1 index c7deb59f1e..8548cbb1ac 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -119,10 +119,6 @@ &npm install >> $log 2>&1 Write-Output ">> $? $($error.Count)" >> $log 2>&1 - Write-Output "### install bower" >> $log 2>&1 - &npm install -g bower >> $log 2>&1 - $error.Clear() # that one fails 'cos bower is deprecated - ignore - Write-Output "### install gulp" >> $log 2>&1 &npm install -g gulp >> $log 2>&1 $error.Clear() # that one fails 'cos deprecated stuff - ignore diff --git a/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial b/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial index 591e1c1885..ccc4d50229 100644 --- a/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial +++ b/src/ApiDocs/umbracotemplate/partials/head.tmpl.partial @@ -8,7 +8,7 @@ {{#_description}}{{/_description}} - + diff --git a/src/ApiDocs/umbracotemplate/styles/main.css b/src/ApiDocs/umbracotemplate/styles/main.css index 7756b2f7d4..d74d51b150 100644 --- a/src/ApiDocs/umbracotemplate/styles/main.css +++ b/src/ApiDocs/umbracotemplate/styles/main.css @@ -63,7 +63,7 @@ a:focus { } .navbar-header .navbar-brand { - background: url(https://our.umbraco.org/assets/images/logo.svg) left center no-repeat; + background: url(https://our.umbraco.com/assets/images/logo.svg) left center no-repeat; background-size: 40px auto; width:50px; } diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index d7f81c1bb1..b5af335791 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2017")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 7c274089f7..c00ab795d2 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -61,7 +61,8 @@ namespace Umbraco.Core.Configuration var config = WebConfigurationManager.OpenWebConfiguration(appPath); var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); - if (settings == null || settings.Smtp == null) return false; + // note: "noreply@example.com" is/was the sample SMTP from email - we'll regard that as "not configured" + if (settings == null || settings.Smtp == null || "noreply@example.com".Equals(settings.Smtp.From, StringComparison.OrdinalIgnoreCase)) return false; if (settings.Smtp.SpecifiedPickupDirectory != null && string.IsNullOrEmpty(settings.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation) == false) return true; if (settings.Smtp.Network != null && string.IsNullOrEmpty(settings.Smtp.Network.Host) == false) diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 6dd5617992..6a1203313e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -193,4 +193,4 @@ namespace Umbraco.Core.Configuration //TODO: Add other configurations here ! } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 39861ac4e9..d2236bab70 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -81,12 +81,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("loginBackgroundImage")] internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); - [ConfigurationProperty("StripUdiAttributes")] - internal InnerTextConfigurationElement StripUdiAttributes - { - get { return GetOptionalTextElement("StripUdiAttributes", true); } - } - string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; @@ -142,7 +136,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes; - bool IContentSection.StripUdiAttributes => StripUdiAttributes; string IContentSection.LoginBackgroundImage => LoginBackgroundImage; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index ef9ffeb014..fe2eea5d91 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -66,7 +66,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnableInheritedMediaTypes { get; } string LoginBackgroundImage { get; } - bool StripUdiAttributes { get; } - } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index ac42274a71..4aa1760a45 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -145,6 +145,15 @@ public const string PartialViewMacros = "partialViewMacros"; + public static class Groups + { + public const string Settings = "settingsGroup"; + + public const string Templating = "templatingGroup"; + + public const string ThirdParty = "thirdPartyGroup"; + } + //TODO: Fill in the rest! } } diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index c9a33ba04d..f2b31be28f 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -13,7 +13,9 @@ public const int Textbox = -88; public const int Boolean = -49; - public const int Datetime = -36; + public const int DateTime = -36; + public const int DropDownSingle = -39; + public const int DropDownMultiple = -42; public const int DefaultContentListView = -95; public const int DefaultMediaListView = -96; diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index c8eedb1614..a0fa732c8c 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -6,10 +6,7 @@ namespace Umbraco.Core.IO //all paths has a starting but no trailing / public class SystemDirectories { - //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net - public static string Bin => IOHelper.ReturnPath("umbracoBinDirectory", "~/bin"); - - public static string Base => IOHelper.ReturnPath("umbracoBaseDirectory", "~/base"); + public static string Bin => "~/bin"; public static string Config => IOHelper.ReturnPath("umbracoConfigDirectory", "~/config"); @@ -19,6 +16,7 @@ namespace Umbraco.Core.IO public static string Install => IOHelper.ReturnPath("umbracoInstallPath", "~/install"); + //TODO: Consider removing this public static string Masterpages => IOHelper.ReturnPath("umbracoMasterPagesPath", "~/masterpages"); //NOTE: this is not configurable and shouldn't need to be @@ -40,9 +38,7 @@ namespace Umbraco.Core.IO public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco"); - [Obsolete("This will be removed, there is no more umbraco_client folder")] - public static string UmbracoClient => IOHelper.ReturnPath("umbracoClientPath", "~/umbraco_client"); - + //TODO: Consider removing this public static string UserControls => IOHelper.ReturnPath("umbracoUsercontrolsPath", "~/usercontrols"); public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 6b8534a88f..d5f6c2b8c4 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Core.Manifest { @@ -19,7 +20,8 @@ namespace Umbraco.Core.Manifest // show: [ // optional, default is always show // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types - // '+media/*' // show for all media types + // '+media/*', // show for all media types + // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, // ... @@ -82,7 +84,7 @@ namespace Umbraco.Core.Manifest public string[] Show { get; set; } = Array.Empty(); /// - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { string partA, partB; @@ -103,15 +105,49 @@ namespace Umbraco.Core.Manifest } var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray()); + var userGroupsList = userGroups.ToList(); - // if no 'show' is specified, then always display the content app - if (rules.Length > 0) + var okRole = false; + var hasRole = false; + var okType = false; + var hasType = false; + + foreach (var rule in rules) { - var ok = false; - - // else iterate over each entry - foreach (var rule in rules) + if (rule.PartA.InvariantEquals("role")) { + // if roles have been ok-ed already, skip the rule + if (okRole) + continue; + + // remember we have role rules + hasRole = true; + + foreach (var group in userGroupsList) + { + // if the entry does not apply, skip + if (!rule.Matches("role", group.Alias)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else ok to display, remember roles are ok, break from userGroupsList + okRole = rule.Show; + break; + } + } + else // it is a type rule + { + // if type has been ok-ed already, skip the rule + if (okType) + continue; + + // remember we have type rules + hasType = true; + // if the entry does not apply, skip it if (!rule.Matches(partA, partB)) continue; @@ -121,16 +157,18 @@ namespace Umbraco.Core.Manifest if (!rule.Show) return null; - // else break - ok to display - ok = true; - break; + // else ok to display, remember type rules are ok + okType = true; } - - // when 'show' is specified, default is to *not* show the content app - if (!ok) - return null; } + // if roles rules are specified but not ok, + // or if type roles are specified but not ok, + // cannot display the content app + if ((hasRole && !okRole) || (hasType && !okType)) + return null; + + // else // content app can be displayed return _app ?? (_app = new ContentApp { diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs index f19fc94c97..1b00b03ca2 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions protected override string GetSql() { - return string.Format(SqlSyntax.AlterColumn, SqlSyntax.GetQuotedTableName(TableName), SqlSyntax.Format(Column)); diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs index 989ce95002..8d95d9c14c 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs @@ -2,6 +2,8 @@ using NPoco; using Umbraco.Core.Migrations.Expressions.Alter.Expressions; using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Migrations.Expressions.Alter.Table @@ -87,6 +89,21 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table public IAlterTableColumnOptionBuilder PrimaryKey() { CurrentColumn.IsPrimaryKey = true; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } @@ -94,6 +111,22 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table { CurrentColumn.IsPrimaryKey = true; CurrentColumn.PrimaryKeyName = primaryKeyName; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + ConstraintName = primaryKeyName, + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index ba491fb5e1..c4ebfb664b 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -114,15 +114,15 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); @@ -149,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); } @@ -242,12 +243,12 @@ namespace Umbraco.Core.Migrations.Install private void CreateDataTypeData() { - void InsertDataTypeDto(int id, string dbType, string configuration = null) + void InsertDataTypeDto(int id, string editorAlias, string dbType, string configuration = null) { var dataTypeDto = new DataTypeDto { NodeId = id, - EditorAlias = Constants.PropertyEditors.Aliases.NoEdit, + EditorAlias = editorAlias, DbType = dbType }; @@ -270,18 +271,18 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); - InsertDataTypeDto(Constants.DataTypes.LabelString, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelInt,"Integer", "{\"umbracoDataValueType\":\"INT\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelBigint, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelDateTime, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelDecimal, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); - InsertDataTypeDto(Constants.DataTypes.LabelTime, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.NoEdit, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.NoEdit, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.NoEdit, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); + InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.NoEdit, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -39, EditorAlias = Constants.PropertyEditors.Aliases.DropDownListMultiple, DbType = "Nvarchar" }); + InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = Constants.PropertyEditors.Aliases.Date, DbType = "Date" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -42, EditorAlias = Constants.PropertyEditors.Aliases.DropDownList, DbType = "Integer" }); + InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}" diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index d45126f07f..28a31f23d5 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -334,16 +334,60 @@ namespace Umbraco.Core.Migrations.Install #region Utilities + /// + /// Returns whether a table with the specified exists in the database. + /// + /// The name of the table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist("MyTable")) + /// { + /// // do something when the table exists + /// } + /// + /// public bool TableExists(string tableName) { return SqlSyntax.DoesTableExist(_database, tableName); } + /// + /// Returns whether the table for the specified exists in the database. + /// + /// The type representing the DTO/table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist<MyDto>) + /// { + /// // do something when the table exists + /// } + /// + /// + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// public bool TableExists() { var table = DefinitionFactory.GetTableDefinition(typeof(T), SqlSyntax); return table != null && TableExists(table.Name); } - // this is used in tests + /// + /// Creates a new table in the database based on the type of . + /// + /// The type representing the DTO/table. + /// Whether the table should be overwritten if it already exists. + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// internal void CreateTable(bool overwrite = false) where T : new() { @@ -351,6 +395,21 @@ namespace Umbraco.Core.Migrations.Install CreateTable(overwrite, tableType, new DatabaseDataCreator(_database, _logger)); } + /// + /// Creates a new table in the database for the specified . + /// + /// Whether the table should be overwritten if it already exists. + /// The the representing the table. + /// + /// + /// If has been decorated with an , the name from + /// that attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation) { var tableDefinition = DefinitionFactory.GetTableDefinition(modelType, SqlSyntax); @@ -364,6 +423,8 @@ namespace Umbraco.Core.Migrations.Install var tableExist = TableExists(tableName); if (overwrite && tableExist) { + _logger.Info($"Table '{tableName}' already exists, but will be recreated"); + DropTable(tableName); tableExist = false; } @@ -417,12 +478,38 @@ namespace Umbraco.Core.Migrations.Install } transaction.Complete(); + + if (overwrite) + { + _logger.Info($"Table '{tableName}' was recreated"); + } + else + { + _logger.Info($"New table '{tableName}' was created"); + } } } - - _logger.Info("Created table '{TableName}'", tableName); + else + { + // The table exists and was not recreated/overwritten. + _logger.Info($"Table '{tableName}' already exists - no changes were made"); + } } + /// + /// Drops the table for the specified . + /// + /// The type representing the DTO/table. + /// + /// + /// schemaHelper.DropTable<MyDto>); + /// + /// + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// public void DropTable(string tableName) { var sql = new Sql(string.Format(SqlSyntax.DropTable, SqlSyntax.GetQuotedTableName(tableName))); diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs index f1c535b466..4c5a1f1aa7 100644 --- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Migrations if (string.IsNullOrWhiteSpace(sql)) { - Logger.Info(GetType(), "SQL [{ContextIndex}: ", Context.Index); + Logger.Info(GetType(), "SQL [{ContextIndex}]: ", Context.Index); } else { diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index eeaf7533a9..5b0838573e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -137,6 +137,7 @@ namespace Umbraco.Core.Migrations.Upgrade // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}... Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); + Chain("{77874C77-93E5-4488-A404-A630907CEEF0}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs new file mode 100644 index 0000000000..fbb233927b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs @@ -0,0 +1,23 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class FixLockTablePrimaryKey : MigrationBase + { + public FixLockTablePrimaryKey(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // at some point, the KeyValueService dropped the PK and failed to re-create it, + // so the PK is gone - make sure we have one, and create if needed + + var constraints = SqlSyntax.GetConstraintsPerTable(Database); + var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock"); + + if (!exists) + Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do(); + } + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 238d87b186..5d8a4f7222 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -227,7 +227,27 @@ namespace Umbraco.Core.Models public bool WasCulturePublished(string culture) // just check _publishInfosOrig - a copy of _publishInfos // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + + // adjust dates to sync between version, cultures etc + // used by the repo when persisting + internal void AdjustDates(DateTime date) + { + foreach (var culture in PublishedCultures.ToList()) + { + if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) + continue; + + if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig) + && publishInfosOrig.Date == publishInfos.Date) + continue; + + _publishInfos[culture] = (publishInfos.Name, date); + + if (CultureNames.TryGetValue(culture, out var name)) + SetCultureInfo(culture, name, date); + } + } /// public bool IsCultureEdited(string culture) @@ -343,6 +363,12 @@ namespace Umbraco.Core.Models SetPublishInfo(c, name, DateTime.Now); } } + else if (culture == null) // invariant culture + { + if (string.IsNullOrWhiteSpace(Name)) + return false; + // PublishName set by repository - nothing to do here + } else // one single culture { var name = GetCultureName(culture); diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs index 5e0c421742..2d30fc6ba9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core.Models.ContentEditing +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Models.ContentEditing { /// /// Represents a content app definition. @@ -15,6 +18,6 @@ /// the content app should be displayed or not, and return either a /// instance, or null. /// - ContentApp GetContentAppFor(object source); + ContentApp GetContentAppFor(object source, IEnumerable userGroups); } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 6f90b5201d..9e73205c36 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -363,22 +363,28 @@ namespace Umbraco.Core.Models /// Alias of the to remove public void RemovePropertyType(string propertyTypeAlias) { - //check if the property exist in one of our collections - if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias)) - || _propertyTypes.Any(x => x.Alias == propertyTypeAlias)) - { - //set the flag that a property has been removed - HasPropertyTypeBeenRemoved = true; - } - + //check through each property group to see if we can remove the property type by alias from it foreach (var propertyGroup in PropertyGroups) { - propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); + if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias)) + { + if (!HasPropertyTypeBeenRemoved) + { + HasPropertyTypeBeenRemoved = true; + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } + break; + } } - if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias)) + //check through each local property type collection (not assigned to a tab) + if (_propertyTypes.RemoveItem(propertyTypeAlias)) { - _propertyTypes.RemoveItem(propertyTypeAlias); + if (!HasPropertyTypeBeenRemoved) + { + HasPropertyTypeBeenRemoved = true; + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } } } @@ -408,23 +414,9 @@ namespace Umbraco.Core.Models /// PropertyTypes that are not part of a PropertyGroup /// [IgnoreDataMember] + //fixme should we mark this as EditorBrowsable hidden since it really isn't ever used? internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public override bool IsPropertyDirty(string propertyName) - { - bool existsInEntity = base.IsPropertyDirty(propertyName); - - bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); - bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); - - return existsInEntity || anyDirtyGroups || anyDirtyTypes; - } - /// /// Indicates whether the current entity is dirty. /// diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index ef55f0d469..0d2f817660 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -1,4 +1,8 @@ -using Umbraco.Core.Models.PublishedContent; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Models { @@ -16,5 +20,63 @@ namespace Umbraco.Core.Models else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member; return itemType; } + + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType) + { + return contentType.WasPropertyTypeVariationChanged(out var _); + } + + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection aliases) + { + var a = new List(); + + // property variation change? + var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType => + { + if (!(propertyType is IRememberBeingDirty dirtyProperty)) + throw new Exception("oops"); + + // skip new properties + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewProperty = dirtyProperty.WasPropertyDirty("Id"); + if (isNewProperty) return false; + + // variation change? + var dirty = dirtyProperty.WasPropertyDirty("Variations"); + if (dirty) + a.Add(propertyType.Alias); + + return dirty; + + }); + + aliases = a; + return hasAnyPropertyVariationChanged; + } + + /// + /// Returns the list of content types the composition is used in + /// + /// + /// + /// + internal static IEnumerable GetWhereCompositionIsUsedInContentTypes(this IContentTypeComposition source, + IContentTypeComposition[] allContentTypes) + { + var sourceId = source != null ? source.Id : 0; + + // find which content types are using this composition + return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray(); + } } } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index ef5988344e..a1d4aee02f 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -21,7 +21,12 @@ namespace Umbraco.Core.Models string Description { get; set; } /// - /// Gets or Sets the Icon for the ContentType + /// Gets or sets the icon for the content type. The value is a CSS class name representing + /// the icon (eg. icon-home) along with an optional CSS class name representing the + /// color (eg. icon-blue). Put together, the value for this scenario would be + /// icon-home color-blue. + /// + /// If a class name for the color isn't specified, the icon color will default to black. /// string Icon { get; set; } diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index b5277a23be..36ace19f0f 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -10,6 +10,7 @@ namespace Umbraco.Core.Models /// /// Gets or sets the content types that compose this content type. /// + //fixme: we should be storing key references, not the object else we are caching way too much IEnumerable ContentTypeComposition { get; set; } /// diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 53b75f7e48..47710e04cb 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -124,10 +124,11 @@ namespace Umbraco.Core.Models return this.Any(x => x.Alias == propertyAlias); } - public void RemoveItem(string propertyTypeAlias) + public bool RemoveItem(string propertyTypeAlias) { var key = IndexOfKey(propertyTypeAlias); if (key != -1) RemoveItem(key); + return key != -1; } public int IndexOfKey(string key) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index e611ded6c8..f1937c1c0c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -94,10 +94,10 @@ namespace Umbraco.Core.Models.PublishedContent { "Comments", (Constants.DataTypes.Textbox, Constants.PropertyEditors.Aliases.TextBox) }, { "IsApproved", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, { "IsLockedOut", (Constants.DataTypes.Boolean, Constants.PropertyEditors.Aliases.Boolean) }, - { "LastLockoutDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "CreateDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "LastLoginDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, - { "LastPasswordChangeDate", (Constants.DataTypes.Datetime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastLockoutDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) }, + { "CreateDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastLoginDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) }, + { "LastPasswordChangeDate", (Constants.DataTypes.DateTime, Constants.PropertyEditors.Aliases.DateTime) }, }; #region Content type diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index c72a89c1f2..67758c1c69 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Models.PublishedContent throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); // have to use an unsafe ctor because we don't know the types, really - var modelCtor = ReflectionUtilities.EmitCtorUnsafe>(constructor); + var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; modelTypeMap[typeName] = type; } @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent if (ctor != null) return ctor(); var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType); return ctor(); } diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs index a6f1dd0f25..556c5203ec 100644 --- a/src/Umbraco.Core/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs @@ -576,7 +576,6 @@ namespace Umbraco.Core.Packaging { //this is experimental and undocumented... path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco); - path = path.Replace("[$UMBRACOCLIENT]", SystemDirectories.UmbracoClient); path = path.Replace("[$CONFIG]", SystemDirectories.Config); path = path.Replace("[$DATA]", SystemDirectories.Data); } diff --git a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs index 833d262e26..b5878141f3 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs @@ -4,12 +4,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { [TableName(Constants.DatabaseSchema.Tables.Lock)] - [PrimaryKey("id")] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class LockDto { [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoLock")] + [PrimaryKeyColumn(Name = "PK_umbracoLock", AutoIncrement = false)] public int Id { get; set; } [Column("value")] diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs index cb47784d92..a3b28b5b54 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos [ExplicitColumns] internal class PropertyDataDto { - private const string TableName = Constants.DatabaseSchema.Tables.PropertyData; + public const string TableName = Constants.DatabaseSchema.Tables.PropertyData; public const int VarcharLength = 512; public const int SegmentLength = 256; diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index d97c748b6f..a5ab62d25f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias); return sql.Where(s, a); } @@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2); return sql.Where(s, a); } @@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence /// Appends an ORDER BY DESC clause to the Sql statement. /// /// The Sql statement. - /// Expression specifying the fields. + /// Fields. /// The Sql statement. - public static Sql OrderByDescending(this Sql sql, params object[] fields) + public static Sql OrderByDescending(this Sql sql, params string[] fields) { return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC"))); } @@ -660,6 +660,18 @@ namespace Umbraco.Core.Persistence return sql.Select(sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields)); } + /// + /// Adds columns to a SELECT Sql statement. + /// + /// The origin sql. + /// Columns to select. + /// The Sql statement. + public static Sql AndSelect(this Sql sql, params string[] fields) + { + if (sql == null) throw new ArgumentNullException(nameof(sql)); + return sql.Append(", " + string.Join(", ", fields)); + } + /// /// Adds columns to a SELECT Sql statement. /// @@ -676,7 +688,6 @@ namespace Umbraco.Core.Persistence return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields))); } - /// /// Adds columns to a SELECT Sql statement. /// @@ -1036,7 +1047,7 @@ namespace Umbraco.Core.Persistence { var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto)); var tableName = tableAlias ?? pd.TableInfo.TableName; - var queryColumns = pd.QueryColumns; + var queryColumns = pd.QueryColumns.ToList(); Dictionary aliases = null; @@ -1056,7 +1067,11 @@ namespace Umbraco.Core.Persistence return fieldName; }).ToArray(); - queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray(); + //only get the columns that exist in the selected names + queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToList(); + + //ensure the order of the columns in the expressions is the order in the result + queryColumns.Sort((a, b) => names.IndexOf(a.Key).CompareTo(names.IndexOf(b.Key))); } string GetAlias(PocoColumn column) diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index f7341d112b..217719e144 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -19,6 +19,12 @@ namespace Umbraco.Core.Persistence.Repositories /// Current version is first, and then versions are ordered with most recent first. IEnumerable GetAllVersions(int nodeId); + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take); + /// /// Gets version identifiers. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index f5e9013082..3bb1ac38ca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -10,6 +10,12 @@ namespace Umbraco.Core.Persistence.Repositories { TItem Get(string alias); IEnumerable> Move(TItem moving, EntityContainer container); + + /// + /// Returns the content types that are direct compositions of the content type + /// + /// The content type id + /// IEnumerable GetTypesDirectlyComposedOf(int id); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 6ace73fbc3..36b7ab07f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -10,7 +9,6 @@ using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; @@ -19,7 +17,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement @@ -56,6 +53,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets all versions, current first public abstract IEnumerable GetAllVersions(int nodeId); + // gets all versions, current first + public virtual IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + => GetAllVersions(nodeId).Skip(skip).Take(take); + // gets all version ids, current first public virtual IEnumerable GetVersionIds(int nodeId, int maxRows) { @@ -255,8 +256,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - var dbfield = GetQuotedFieldName("umbracoNode", "id"); - (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?! + var (dbfield, _) = SqlContext.VisitDto(x => x.NodeId); if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased? @@ -265,6 +265,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); + + // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) + if (!ordering.Culture.IsNullOrWhiteSpace()) + { + for (var i = 0; i < psql.Arguments.Length; i++) + { + if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]") + { + psql.Arguments[i] = ordering.Culture; + break; + } + } + } return psql; } @@ -342,20 +355,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); - // culture = must work on variant name ?? invariant name - // insert proper join and return coalesced ordering field - - var joins = Sql() - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); - - // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments); - - sql = InsertJoins(sql, joins); - - return "ordering"; + // "variantName" alias is defined in DocumentRepository.GetBaseQuery + // fixme - what if it is NOT a document but a ... media or whatever? + // previously, we inserted the join+select *here* so we were sure to have it, + // but now that's not the case anymore! + return "variantName"; } // previously, we'd accept anything and just sanitize it - not anymore @@ -608,6 +612,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region UnitOfWork Events + //fixme: The reason these events are in the repository is for legacy, the events should exist at the service + // level now since we can fire these events within the transaction... so move the events to service level + public class ScopedEntityEventArgs : EventArgs { public ScopedEntityEventArgs(IScope scope, TEntity entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 555fc56157..3f1ea3116e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -274,6 +274,8 @@ AND umbracoNode.id <> @id", if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && compositionBase.RemovedContentTypeKeyTracker.Any()) { + //TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating? + // find Content based on the current ContentType var sql = Sql() .SelectAll() @@ -292,6 +294,7 @@ AND umbracoNode.id <> @id", // based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) { + //TODO: This could be done with bulk SQL statements foreach (var propertyType in propertyTypes) { var nodeId = contentDto.NodeId; @@ -323,9 +326,7 @@ AND umbracoNode.id <> @id", }); } - // fixme below, manage the property type - - // delete ??? fixme wtf is this? + // delete property types // ... by excepting entries from db with entries from collections if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { @@ -404,10 +405,49 @@ AND umbracoNode.id <> @id", propertyType.PropertyGroupId = new Lazy(() => groupId); } + //check if the content type variation has been changed + var ctVariationChanging = entity.IsPropertyDirty("Variations"); + if (ctVariationChanging) + { + //we've already looked up the previous version of the content type so we know it's previous variation state + MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations); + Clear301Redirects(entity); + ClearScheduledPublishing(entity); + } + + //track any content type/property types that are changing variation which will require content updates + var propertyTypeVariationChanges = new Dictionary(); + // insert or update properties // all of them, no-group and in-groups foreach (var propertyType in entity.PropertyTypes) { + //if the content type variation isn't changing track if any property type is changing + if (!ctVariationChanging) + { + if (propertyType.IsPropertyDirty("Variations")) + { + propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations; + } + } + else + { + switch(entity.Variations) + { + case ContentVariation.Nothing: + //if the content type is changing to Nothing, then all property type's must change to nothing + propertyType.Variations = ContentVariation.Nothing; + break; + case ContentVariation.Culture: + //we don't need to modify the property type in this case + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } + var groupId = propertyType.PropertyGroupId?.Value ?? default(int); // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default(int)) @@ -431,6 +471,28 @@ AND umbracoNode.id <> @id", orphanPropertyTypeIds.Remove(typeId); } + //check if any property types were changing variation + if (propertyTypeVariationChanges.Count > 0) + { + var changes = new Dictionary(); + + //now get the current property type variations for the changed ones so that we know which variation they + //are going from and to + var from = Database.Dictionary(Sql() + .Select(x => x.Id, x => x.Variations) + .From() + .WhereIn(x => x.Id, propertyTypeVariationChanges.Keys)); + + foreach (var f in from) + { + changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value); + } + + //perform the move + MoveVariantData(changes); + } + + // deal with orphan properties: those that were in a deleted tab, // and have not been re-mapped to another tab or to 'generic properties' if (orphanPropertyTypeIds != null) @@ -438,6 +500,221 @@ AND umbracoNode.id <> @id", DeletePropertyType(entity.Id, id); } + /// + /// Clear any redirects associated with content for a content type + /// + private void Clear301Redirects(IContentTypeComposition contentType) + { + //first clear out any existing property data that might already exists under the default lang + var sqlSelect = Sql().Select(x => x.UniqueId) + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + var sqlDelete = Sql() + .Delete() + .WhereIn((System.Linq.Expressions.Expression>)(x => x.ContentKey), sqlSelect); + + Database.Execute(sqlDelete); + } + + /// + /// Clear any scheduled publishing associated with content for a content type + /// + private void ClearScheduledPublishing(IContentTypeComposition contentType) + { + //TODO: Fill this in when scheduled publishing is enabled for variants + } + + /// + /// Moves variant data for property type changes + /// + /// + private void MoveVariantData(IDictionary propertyTypeChanges) + { + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault)); + + //Group by the "To" variation so we can bulk update in the correct batches + foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2)) + { + var propertyTypeIds = g.Select(s => s.Key).ToList(); + + //the ContentVariation that the data is moving "To" + var toVariantType = g.Key; + + switch(toVariantType) + { + case ContentVariation.Culture: + MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds); + break; + case ContentVariation.Nothing: + MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds); + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } + } + + /// + /// Moves variant data for a content type variation change + /// + /// + /// + /// + private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to) + { + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault)); + + var sqlPropertyTypeIds = Sql().Select(x => x.Id).From().Where(x => x.ContentTypeId == contentType.Id); + switch (to) + { + case ContentVariation.Culture: + //move the property data + + MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + + //now we need to move the names + //first clear out any existing names that might already exists under the default lang + //there's 2x tables to update + + //clear out the versionCultureVariation table + var sqlSelect = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.Id, x => x.VersionId) + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id) + .Where(x => x.LanguageId == defaultLangId); + var sqlDelete = Sql() + .Delete() + .WhereIn(x => x.Id, sqlSelect); + Database.Execute(sqlDelete); + + //clear out the documentCultureVariation table + sqlSelect = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id) + .Where(x => x.LanguageId == defaultLangId); + sqlDelete = Sql() + .Delete() + .WhereIn(x => x.Id, sqlSelect); + Database.Execute(sqlDelete); + + //now we need to insert names into these 2 tables based on the invariant data + + //insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang + var cols = Sql().Columns(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId); + sqlSelect = Sql().Select(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate) + .Append($", {defaultLangId}") //default language ID + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect); + Database.Execute(sqlInsert); + + //insert rows into the documentCultureVariation table + cols = Sql().Columns(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId); + sqlSelect = Sql().Select(x => x.NodeId, x => x.Edited, x => x.Published) + .AndSelect(x => x.Text) + .Append($", 1, {defaultLangId}") //make Available + default language ID + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect); + Database.Execute(sqlInsert); + + break; + case ContentVariation.Nothing: + //move the property data + + MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + + //we dont need to move the names! this is because we always keep the invariant names with the name of the default language. + + //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :( + // if we want these SQL statements back, look into GIT history + + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } + + /// + /// This will move all property data from variant to invariant + /// + /// + /// Optional list of property type ids of the properties to be updated + /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated + private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) + { + //first clear out any existing property data that might already exists under the default lang + var sqlDelete = Sql() + .Delete() + .Where(x => x.LanguageId == null); + if (sqlPropertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + Database.Execute(sqlDelete); + + //now insert all property data into the default language that exists under the invariant lang + var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId); + var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue) + .Append(", NULL") //null language ID + .From() + .Where(x => x.LanguageId == defaultLangId); + if (sqlPropertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData); + + Database.Execute(sqlInsert); + } + + /// + /// This will move all property data from invariant to variant + /// + /// + /// Optional list of property type ids of the properties to be updated + /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated + private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) + { + //first clear out any existing property data that might already exists under the default lang + var sqlDelete = Sql() + .Delete() + .Where(x => x.LanguageId == defaultLangId); + if (sqlPropertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + Database.Execute(sqlDelete); + + //now insert all property data into the default language that exists under the invariant lang + var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId); + var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue) + .Append($", {defaultLangId}") //default language ID + .From() + .Where(x => x.LanguageId == null); + if (sqlPropertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData); + + Database.Execute(sqlInsert); + } + private void DeletePropertyType(int contentTypeId, int propertyTypeId) { // first clear dependencies @@ -571,8 +848,11 @@ AND umbracoNode.id <> @id", } } + /// public IEnumerable GetTypesDirectlyComposedOf(int id) { + //fixme - this will probably be more efficient to simply load all content types and do the calculation, see GetWhereCompositionIsUsedInContentTypes + var sql = Sql() .SelectAll() .From() diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index bf41cd1ad1..8aa99e8ce0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetBaseQuery(queryType, true); } + // gets the COALESCE expression for variant/invariant name + private string VariantNameSqlExpression + => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); @@ -110,12 +114,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(documentDto => documentDto.ContentDto, r1 => - r1.Select(contentDto => contentDto.NodeDto)) - .Select(documentDto => documentDto.DocumentVersionDto, r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) - .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))); + r.Select(documentDto => documentDto.ContentDto, r1 => + r1.Select(contentDto => contentDto.NodeDto)) + .Select(documentDto => documentDto.DocumentVersionDto, r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) + .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) + + // select the variant name, coalesce to the invariant name, as "variantName" + .AndSelect(VariantNameSqlExpression + " AS variantName"); break; } @@ -125,13 +132,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On(left => left.NodeId, right => right.NodeId) // inner join on mandatory edited version - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.Id == right.Id) + .InnerJoin() + .On((left, right) => left.NodeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.Id == right.Id) // left join on optional published version .LeftJoin(nested => - nested.InnerJoin("pdv").On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") - .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + nested.InnerJoin("pdv") + .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv") + + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -204,6 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return MapDtosToContent(Database.Fetch(sql), true); } + public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + { + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); + + return MapDtosToContent(Database.Fetch(sql), true, true); + } + public override IContent GetVersion(int versionId) { var sql = GetBaseQuery(QueryType.Single, false) @@ -341,6 +367,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // persist the variations if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) @@ -413,7 +443,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // whatever we do, we must check that we are saving the current version // fixme maybe we can just fetch Current (bool) var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault(); - if (version == null || !version.Current ) + if (version == null || !version.Current) throw new InvalidOperationException("Cannot save a non-current version."); // update @@ -499,6 +529,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) @@ -682,16 +716,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, + long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { Sql filterSql = null; + // if we have a filter, map its clauses to an Sql statement if (filter != null) { + // if the clause works on "name", we need to swap the field and use the variantName instead, + // so that querying also works on variant content (for instance when searching a listview). + + // figure out how the "name" field is going to look like - so we can look for it + var nameField = SqlContext.VisitModelField(x => x.Name); + filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) - filterSql.Append($"AND ({filterClause.Item1})", filterClause.Item2); + { + var clauseSql = filterClause.Item1; + var clauseArgs = filterClause.Item2; + + // replace the name field + // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here + clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression); + + // append the clause + filterSql.Append($"AND ({clauseSql})", clauseArgs); + } } return GetPage(query, pageIndex, pageSize, out totalRecords, @@ -828,7 +879,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser"); // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments); + sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering "), sql.Arguments); sql = InsertJoins(sql, joins); @@ -850,10 +901,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // variant: left join may yield NULL or something, and that determines published var joins = Sql() - .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); sql = InsertJoins(sql, joins); @@ -873,7 +921,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return base.ApplySystemOrdering(ref sql, ordering); } - private IEnumerable MapDtosToContent(List dtos, bool withCache = false) + private IEnumerable MapDtosToContent(List dtos, bool withCache = false, bool slim = false) { var temps = new List>(); var contentTypes = new Dictionary(); @@ -906,18 +954,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - // need templates - var templateId = dto.DocumentVersionDto.TemplateId; - if (templateId.HasValue && templateId.Value > 0) - templateIds.Add(templateId.Value); - if (dto.Published) + if (!slim) { - templateId = dto.PublishedVersionDto.TemplateId; + // need templates + var templateId = dto.DocumentVersionDto.TemplateId; if (templateId.HasValue && templateId.Value > 0) templateIds.Add(templateId.Value); + if (dto.Published) + { + templateId = dto.PublishedVersionDto.TemplateId; + if (templateId.HasValue && templateId.Value > 0) + templateIds.Add(templateId.Value); + } } - // need properties + // need temps, for properties, templates and variations var versionId = dto.DocumentVersionDto.Id; var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType, c) @@ -928,25 +979,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement temps.Add(temp); } - // load all required templates in 1 query, and index - var templates = _templateRepository.GetMany(templateIds.ToArray()) - .ToDictionary(x => x.Id, x => x); - - // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - - // assign templates and properties - foreach (var temp in temps) + if (!slim) { - // complete the item - if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) - temp.Content.Template = template; - if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) - temp.Content.PublishTemplate = template; - temp.Content.Properties = properties[temp.VersionId]; + // load all required templates in 1 query, and index + var templates = _templateRepository.GetMany(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); - // reset dirty initial properties (U4-1946) - temp.Content.ResetDirtyProperties(false); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); + + // assign templates and properties + foreach (var temp in temps) + { + // complete the item + if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) + temp.Content.Template = template; + if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) + temp.Content.PublishTemplate = template; + + if (properties.ContainsKey(temp.VersionId)) + temp.Content.Properties = properties[temp.VersionId]; + else + throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'."); + + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); + } } // set variations, if varying @@ -1220,7 +1278,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // of whether the name has changed (ie the culture has been updated) - some saving culture // fr-FR could cause culture en-UK name to change - not sure that is clean - foreach(var (culture, name) in content.CultureNames) + foreach (var (culture, name) in content.CultureNames) { var langId = LanguageRepository.GetIdByIsoCode(culture); if (!langId.HasValue) continue; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 2b3674700b..e316d1d04b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false); } - private FullDataSetRepositoryCachePolicy TypedCachePolicy => (FullDataSetRepositoryCachePolicy) CachePolicy; + private FullDataSetRepositoryCachePolicy TypedCachePolicy => CachePolicy as FullDataSetRepositoryCachePolicy; #region Overrides of RepositoryBase @@ -225,7 +225,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public ILanguage GetByIsoCode(string isoCode) { - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + + var id = GetIdByIsoCode(isoCode, throwOnNotFound: false); return id.HasValue ? Get(id.Value) : null; } @@ -238,7 +242,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { if (isoCode == null) return null; - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + else + PerformGetAll(); //we don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap + lock (_codeIdMap) { if (_codeIdMap.TryGetValue(isoCode, out var id)) return id; @@ -256,7 +265,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { if (id == null) return null; - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + else + PerformGetAll(); + lock (_codeIdMap) // yes, we want to lock _codeIdMap { if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode; @@ -279,8 +293,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // do NOT leak that language, it's not deep-cloned! private ILanguage GetDefault() { - // get all cached, non-cloned - var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList(); + // get all cached + var languages = (TypedCachePolicy?.GetAllCached(PerformGetAll) //try to get all cached non-cloned if using the correct cache policy (not the case in unit tests) + ?? CachePolicy.GetAll(Array.Empty(), PerformGetAll)).ToList(); + var language = languages.FirstOrDefault(x => x.IsDefault); if (language != null) return language; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 2390ce9a7b..dbfdc8e980 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Many: sql = sql.Select(r => r.Select(x => x.NodeDto) - .Select(x => x.ContentVersionDto)); + .Select(x => x.ContentVersionDto)) + + // ContentRepositoryBase expects a variantName field to order by name + // for now, just return the plain invariant node name + // fixme media should support variants !! + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index e6783eaf6d..e80faaa44a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IMemberGroup PerformGet(int id) { var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.Where(GetBaseWhereClause(), new { id = id }); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); @@ -262,7 +262,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase); foreach (var toAssign in nonAssignedRoles) { - var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId; + var groupId = rolesForNames.First(x => x.Text.InvariantEquals(toAssign)).NodeId; Database.Insert(new Member2MemberGroupDto { Member = mId, MemberGroup = groupId }); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 84ef154ae8..fd79b231de 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,12 +6,12 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(x => x.ContentVersionDto) - .Select(x => x.ContentDto, r1 => - r1.Select(x => x.NodeDto))); + r.Select(x => x.ContentVersionDto) + .Select(x => x.ContentDto, r1 => + r1.Select(x => x.NodeDto))) + + // ContentRepositoryBase expects a variantName field to order by name + // so get it here, though for members it's just the plain node name + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs index e28816b6a4..249e2cafd0 100644 --- a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs @@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression. + /// + /// The type of the model. + /// An . + /// An expression to visit. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) VisitModel(this ISqlContext sqlContext, Expression> expression) + { + var visitor = new ModelToSqlExpressionVisitor(sqlContext.SqlSyntax, sqlContext.Mappers); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression representing a field. + /// + /// The type of the model. + /// An . + /// An expression to visit, representing a field. + /// The name of the field. + public static string VisitModelField(this ISqlContext sqlContext, Expression> field) + { + var (sql, _) = sqlContext.VisitModel(field); + + // going to return " = @0" + // take the first part only + var pos = sql.IndexOf(' '); + return sql.Substring(0, pos); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs index 762b6dd7dd..4df11e4f60 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { - var asString = value.ToString(); + var asString = value == null ? "" : value.ToString(); var emailVal = new EmailAddressAttribute(); diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index a5acfe78e3..870cb9ec13 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. - public static TLambda EmitCtor(bool mustExist = true, Type declaring = null) + public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); @@ -313,7 +313,7 @@ namespace Umbraco.Core } // emit - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } /// @@ -325,16 +325,16 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtor(ConstructorInfo ctor) + public static TLambda EmitConstructor(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } - private static TLambda EmitCtorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) + private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) { // get type and args var ctorDeclaring = ctor.DeclaringType; @@ -350,7 +350,7 @@ namespace Umbraco.Core ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); // emit - return EmitCtor(ctorDeclaring, ctorParameters, ctor); + return EmitConstructor(ctorDeclaring, ctorParameters, ctor); } /// @@ -367,17 +367,17 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtorUnsafe(ConstructorInfo ctor) + public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); // emit - unsafe - use lambda's args and assume they are correct - return EmitCtor(lambdaReturned, lambdaParameters, ctor); + return EmitConstructor(lambdaReturned, lambdaParameters, ctor); } - private static TLambda EmitCtor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) + private static TLambda EmitConstructor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) { // gets the method argument types var ctorParameters = GetParameters(ctor); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 0ad621b97c..5464961f16 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Runtime { @@ -304,28 +305,26 @@ namespace Umbraco.Core.Runtime throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); } - // if we already know we want to upgrade, no need to look for migrations... - if (_state.Level == RuntimeLevel.Upgrade) - return; + // if we already know we want to upgrade, + // still run EnsureUmbracoUpgradeState to get the states + // (v7 will just get a null state, that's ok) // else // look for a matching migration entry - bypassing services entirely - they are not 'up' yet // fixme - in a LB scenario, ensure that the DB gets upgraded only once! - bool exists; + bool noUpgrade; try { - exists = EnsureUmbracoUpgradeState(databaseFactory, logger); + noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); } catch (Exception e) { - // can connect to the database but cannot access the migration table... need to install + // can connect to the database but cannot check the upgrade state... oops logger.Warn(e, "Could not check the upgrade state."); - logger.Debug("Could not check the upgrade state, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - return; + throw new BootFailedException("Could not check the upgrade state.", e); } - if (exists) + if (noUpgrade) { // the database version matches the code & files version, all clear, can run _state.Level = RuntimeLevel.Run; @@ -343,27 +342,19 @@ namespace Umbraco.Core.Runtime protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - // no scope, no key value service - just directly accessing the database - var umbracoPlan = new UmbracoPlan(); var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); - string state; + // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - var sql = databaseFactory.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == stateValueKey); - state = database.FirstOrDefault(sql)?.Value; + _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + _state.FinalMigrationState = umbracoPlan.FinalState; } - _state.CurrentMigrationState = state; - _state.FinalMigrationState = umbracoPlan.FinalState; + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, state ?? ""); - - return state == _state.FinalMigrationState; + return _state.CurrentMigrationState == _state.FinalMigrationState; } #region Locals diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs index 9e82213aa5..66b3982b49 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -109,23 +109,7 @@ namespace Umbraco.Core.Services return new ContentTypeAvailableCompositionsResults(ancestors, result); } - /// - /// Returns the list of content types the composition is used in - /// - /// - /// - /// - /// - internal static IEnumerable GetWhereCompositionIsUsedInContentTypes(this IContentTypeService ctService, - IContentTypeComposition source, - IContentTypeComposition[] allContentTypes) - { - - var sourceId = source != null ? source.Id : 0; - - // find which content types are using this composition - return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray(); - } + private static IContentTypeComposition[] GetAncestors(IContentTypeComposition ctype, IContentTypeComposition[] allContentTypes) { diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs index 5b6976c021..691a3a0b63 100644 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -40,7 +42,7 @@ namespace Umbraco.Core.Services /// /// Returns a ApplicationTree Array IEnumerable GetAll(); - + /// /// Gets the application tree for the applcation with the specified alias /// @@ -55,6 +57,14 @@ namespace Umbraco.Core.Services /// /// Returns a ApplicationTree Array IEnumerable GetApplicationTrees(string applicationAlias, bool onlyInitialized); + + /// + /// Gets the grouped application trees for the application with the specified alias + /// + /// + /// + /// + IDictionary> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized); } /// @@ -113,6 +123,11 @@ namespace Umbraco.Core.Services throw new System.NotImplementedException(); } + public IDictionary> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized) + { + throw new System.NotImplementedException(); + } + /// /// Gets the application tree for the applcation with the specified alias /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 022bee8b41..64877e393e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -134,6 +134,12 @@ namespace Umbraco.Core.Services /// Versions are ordered with current first, then most recent first. IEnumerable GetVersions(int id); + /// + /// Gets all versions of a document. + /// + /// Versions are ordered with current first, then most recent first. + IEnumerable GetVersionsSlim(int id, int skip, int take); + /// /// Gets top versions of a document. /// @@ -461,5 +467,21 @@ namespace Umbraco.Core.Services IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); #endregion + + #region Rollback + + /// + /// Rolls back the content to a specific version. + /// + /// The id of the content node. + /// The version id to roll back to. + /// An optional culture to roll back. + /// The identifier of the user who is performing the roll back. + /// + /// When no culture is specified, all cultures are rolled back. + /// + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..5b65c84b1b 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -474,6 +474,19 @@ namespace Umbraco.Core.Services.Implement } } + /// + /// Gets a collection of an objects versions by Id + /// + /// An Enumerable list of objects + public IEnumerable GetVersionsSlim(int id, int skip, int take) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _documentRepository.GetAllVersionsSlim(id, skip, take); + } + } + /// /// Gets a list of all version Ids for the given content item ordered so latest is first /// @@ -1825,6 +1838,8 @@ namespace Umbraco.Core.Services.Implement sendToPublishEventArgs.CanCancel = false; scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + scope.Complete(); } return true; @@ -1930,7 +1945,7 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents && published.Any()) scope.Events.Dispatch(Published, this, new PublishEventArgs(published, false, false), "Published"); - Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); + Audit(AuditType.Sort, "Sort child items performed by user", userId, itemsA[0].ParentId); return true; } @@ -2490,5 +2505,68 @@ namespace Umbraco.Core.Services.Implement } #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + //Get the current copy of the node + var content = GetById(id); + + //Get the version + var version = GetVersion(versionId); + + //Good ole null checks + if (content == null || version == null) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + //Store the result of doing the save of content for the rollback + OperationResult rollbackSaveResult; + + using (var scope = ScopeProvider.CreateScope()) + { + var rollbackEventArgs = new RollbackEventArgs(content); + + //Emit RollingBack event aka before + if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } + + //Copy the changes from the version + content.CopyFrom(version, culture); + + //Save the content for the rollback + rollbackSaveResult = Save(content, userId); + + //Depending on the save result - is what we log & audit along with what we return + if (rollbackSaveResult.Success == false) + { + //Log the error/warning + Logger.Error("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + } + else + { + //Emit RolledBack event aka after + rollbackEventArgs.CanCancel = false; + scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + + //Logging & Audit message + Logger.Info("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id); + } + + scope.Complete(); + } + + return rollbackSaveResult; + } + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index 9fa9a47003..33fb9a0894 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -20,12 +20,12 @@ namespace Umbraco.Core.Services.Implement internal static event TypedEventHandler.EventArgs> Changed; // that one is always immediate (transactional) - public static event TypedEventHandler.EventArgs> UowRefreshedEntity; + public static event TypedEventHandler.EventArgs> ScopedRefreshedEntity; // used by tests to clear events internal static void ClearScopeEvents() { - UowRefreshedEntity = null; + ScopedRefreshedEntity = null; } // these must be dispatched @@ -48,7 +48,7 @@ namespace Umbraco.Core.Services.Implement protected void OnUowRefreshedEntity(ContentTypeChange.EventArgs args) { // that one is always immediate (not dispatched, transactional) - UowRefreshedEntity.RaiseEvent(args, This); + ScopedRefreshedEntity.RaiseEvent(args, This); } protected bool OnSavingCancelled(IScope scope, SaveEventArgs args) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 8ed0a0f645..a114f415cc 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -118,6 +118,8 @@ namespace Umbraco.Core.Services.Implement // - content type alias changed // - content type property removed, or alias changed // - content type composition removed (not testing if composition had properties...) + // - content type variation changed + // - property type variation changed // // because these are the changes that would impact the raw content data @@ -132,7 +134,8 @@ namespace Umbraco.Core.Services.Implement var dirty = (IRememberBeingDirty)contentType; // skip new content types - var isNewContentType = dirty.WasPropertyDirty("HasIdentity"); + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewContentType = dirty.WasPropertyDirty("Id"); if (isNewContentType) { AddChange(changes, contentType, ContentTypeChangeTypes.Create); @@ -149,12 +152,12 @@ namespace Umbraco.Core.Services.Implement throw new Exception("oops"); // skip new properties - var isNewProperty = dirtyProperty.WasPropertyDirty("HasIdentity"); + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewProperty = dirtyProperty.WasPropertyDirty("Id"); if (isNewProperty) return false; // alias change? - var hasPropertyAliasBeenChanged = dirtyProperty.WasPropertyDirty("Alias"); - return hasPropertyAliasBeenChanged; + return dirtyProperty.WasPropertyDirty("Alias"); }); // removed properties? @@ -163,8 +166,15 @@ namespace Umbraco.Core.Services.Implement // removed compositions? var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved"); + // variation changed? + var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations"); + + // property variation change? + var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged(); + // main impact on properties? - var hasPropertyMainImpact = hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; + var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged + || hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; if (hasAliasChanged || hasPropertyMainImpact) { @@ -336,6 +346,9 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetComposedOf(int id) { + //fixme: this is essentially the same as ContentTypeServiceExtensions.GetWhereCompositionIsUsedInContentTypes which loads + // all content types to figure this out, this instead makes quite a few queries so should be replaced + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(ReadLockIds); diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index cb1c423535..e0d78b0258 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Expressions.Create; using Umbraco.Core.Scoping; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -35,23 +34,23 @@ namespace Umbraco.Core.Services.Implement private void Initialize() { - // all this cannot be achieved via an UmbracoPlan migration since it needs to - // run before any migration, in order to figure out the current plan's state. - // (does not prevent us from using a migration to do it, though) + // the key/value service is entirely self-managed, because it is used by the + // upgrader and anything we might change need to happen before everything else + + // if already running 8, either following an upgrade or an install, + // then everything should be ok (the table should exist, etc) + + if (UmbracoVersion.Local.Major >= 8) + return; + + // else we are upgrading from 7, we can assume that the locks table + // exists, but we need to create everything for key/value using (var scope = _scopeProvider.CreateScope()) { - // assume that if the lock object for key/value exists, then everything is ok - if (scope.Database.Exists(Constants.Locks.KeyValues)) - { - scope.Complete(); - return; - } - var context = new MigrationContext(scope.Database, _logger); var initMigration = new InitializeMigration(context); initMigration.Migrate(); - scope.Complete(); } } @@ -59,7 +58,7 @@ namespace Umbraco.Core.Services.Implement /// /// A custom migration that executes standalone during the Initialize phase of this service. /// - private class InitializeMigration : MigrationBase + internal class InitializeMigration : MigrationBase { public InitializeMigration(IMigrationContext context) : base(context) @@ -67,26 +66,47 @@ namespace Umbraco.Core.Services.Implement public override void Migrate() { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + // create a temp. id column and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set nid = id").Do(); + // drop the id column entirely (cannot just drop identity) Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // recreate the id column without identity and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set id = nid").Do(); + // drop the temp. id column Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // complete the primary key Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - - // insert the key-value lock - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); - - // create the key-value table if it's not there - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue) == false) - Create.Table().Do(); } } @@ -169,5 +189,22 @@ namespace Umbraco.Core.Services.Implement return true; } + + /// + /// Gets a value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + internal static string GetValue(IUmbracoDatabase database, string key) + { + // not 8 yet = no key/value table, no value + if (UmbracoVersion.Local.Major < 8) + return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } } } diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 3afb7c3777..cc76374715 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -72,7 +72,7 @@ namespace Umbraco.Core.Services.Implement // users being (dis)approved = not an issue, filtered in memory not in SQL // users being modified or created = not an issue, ordering by ID, as long as we don't *insert* low IDs // users being deleted = not an issue for GetNextUsers - var id = 0; + var id = Constants.Security.SuperUserId; var nodeIds = content.Path.Split(',').Select(int.Parse).ToArray(); const int pagesz = 400; // load batches of 400 users do diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index dcf6832b6c..39b92ebdbd 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -364,6 +364,7 @@ + diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 4855b161df..1d5876187b 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Benchmarks // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( - _emittedCtor = ReflectionUtilities.EmitCtor>(); + _emittedCtor = ReflectionUtilities.EmitConstuctor>(); } public IFoo IlCtor(IFoo foo) diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 1f7f164b21..46dae8bcfd 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -13,16 +13,16 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitCtor>(); + var ctor1 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class1)); + var ctor2 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitCtor>(); + var ctor3 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class3)); + var ctor4 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } @@ -30,40 +30,40 @@ namespace Umbraco.Tests.Clr public void EmitCtorEmitsFromInfo() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - var ctor1 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor1 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor1()); ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new[] { typeof(int) }, null); - var ctor3 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor3 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor3(42)); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitCtor>(); + var ctor = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] public void EmitCtorThrowsIfNotFound() { - Assert.Throws(() => ReflectionUtilities.EmitCtor>()); + Assert.Throws(() => ReflectionUtilities.EmitConstuctor>()); } [Test] public void EmitCtorThrowsIfInvalid() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorReturnsNull() { - Assert.IsNull(ReflectionUtilities.EmitCtor>(false)); + Assert.IsNull(ReflectionUtilities.EmitConstuctor>(false)); } [Test] diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index a8624e8871..955f6f94c8 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); - Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static ProfilingLogger GetTestProfilingLogger() diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index d7f2e7dd53..9b23ec3d6b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -275,13 +275,6 @@ AnotherContentFinder Assert.AreEqual(34, actions.Count()); } - [Test] - public void Resolves_Trees() - { - var trees = _typeLoader.GetTrees(); - Assert.AreEqual(1, trees.Count()); - } - [Test] public void GetDataEditors() { diff --git a/src/Umbraco.Tests/IO/IoHelperTests.cs b/src/Umbraco.Tests/IO/IoHelperTests.cs index 07436eff1a..b2ef5e4d31 100644 --- a/src/Umbraco.Tests/IO/IoHelperTests.cs +++ b/src/Umbraco.Tests/IO/IoHelperTests.cs @@ -43,8 +43,7 @@ namespace Umbraco.Tests.IO Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Preview, true), IOHelper.MapPath(SystemDirectories.Preview, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Root, true), IOHelper.MapPath(SystemDirectories.Root, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Scripts, true), IOHelper.MapPath(SystemDirectories.Scripts, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Umbraco, true), IOHelper.MapPath(SystemDirectories.Umbraco, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.UmbracoClient, true), IOHelper.MapPath(SystemDirectories.UmbracoClient, false)); + Assert.AreEqual(IOHelper.MapPath(SystemDirectories.Umbraco, true), IOHelper.MapPath(SystemDirectories.Umbraco, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.UserControls, true), IOHelper.MapPath(SystemDirectories.UserControls, false)); Assert.AreEqual(IOHelper.MapPath(SystemDirectories.WebServices, true), IOHelper.MapPath(SystemDirectories.WebServices, false)); } diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs new file mode 100644 index 0000000000..eed0919149 --- /dev/null +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Manifest +{ + [TestFixture] + public class ManifestContentAppTests + { + [Test] + public void Test() + { + var contentType = Mock.Of(); + Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); + var content = Mock.Of(); + Mock.Get(content).Setup(x => x.ContentType).Returns(contentType); + + var group1 = Mock.Of(); + Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); + var group2 = Mock.Of(); + Mock.Get(group2).Setup(x => x.Alias).Returns("group2"); + + // no rule = ok + AssertDefinition(content, true, Array.Empty(), new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new [] { "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-content/type1" }, new [] { group1, group2 }); + + // when there are type rules, failing to approve the type = no app + AssertDefinition(content, false, new[] { "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/type1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new [] { group1, group2 }); + + // when there are role rules, failing to approve a role = no app + AssertDefinition(content, false, new[] { "+role/group33" }, new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new[] { "+role/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+role/group1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-role/group1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new [] { group1, group2 }); + + // mixed type and role rules, both are evaluated and need to match + AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new [] { group1, group2 }); + } + + private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) + { + var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); + var app = definition.GetContentAppFor(source, groups); + if (expected) + Assert.IsNotNull(app); + else + Assert.IsNull(app); + } + } +} diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index 4dfb90c8fd..819dbc89ed 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -25,12 +25,9 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { var database = scope.Database; - database.Execute("SET IDENTITY_INSERT umbracoLock ON"); database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); - database.Execute("SET IDENTITY_INSERT umbracoLock OFF"); - database.CompleteTransaction(); scope.Complete(); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e4f794763d..fa15391615 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -63,8 +63,7 @@ namespace Umbraco.Tests.Persistence.Repositories } //TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). - - + [Test] public void Maps_Templates_Correctly() { @@ -377,7 +376,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(contentType); - var dirty = ((ICanBeDirty)contentType).IsDirty(); + var dirty = contentType.IsDirty(); // Assert Assert.That(contentType.HasIdentity, Is.True); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs similarity index 99% rename from src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs rename to src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index c4105ba97e..260b539df7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ContentRepositoryTest : TestWithDatabaseBase + public class DocumentRepositoryTest : TestWithDatabaseBase { public override void SetUp() { diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index cde161132a..27ff0b6d76 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using System.Reflection; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Cache; using Umbraco.Core.Composing; @@ -2108,7 +2109,7 @@ namespace Umbraco.Tests.Services var rollback = contentService.GetById(NodeDto.NodeIdSeed + 4); var rollto = contentService.GetVersion(version1); rollback.CopyFrom(rollto); - rollback.Name = rollto.Name; // must do it explicitely + rollback.Name = rollto.Name; // must do it explicitly contentService.Save(rollback); Assert.IsNotNull(rollback); @@ -2158,6 +2159,210 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Jane Doe", content.GetValue("author")); } + [Test] + public void Can_Rollback_Version_On_Multilingual() + { + var langFr = new Language("fr"); + var langDa = new Language("da"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langDa); + + var contentType = MockedContentTypes.CreateSimpleContentType("multi", "Multi"); + contentType.Key = new Guid("45FF9A70-9C5F-448D-A476-DCD23566BBF8"); + contentType.Variations = ContentVariation.Culture; + var p1 = contentType.PropertyTypes.First(); + p1.Variations = ContentVariation.Culture; + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! + ServiceContext.ContentTypeService.Save(contentType); + + var page = new Content("Page", -1, contentType) + { + Level = 1, + SortOrder = 1, + CreatorId = 0, + WriterId = 0, + Key = new Guid("D7B84CC9-14AE-4D92-A042-023767AD3304") + }; + + page.SetCultureName("fr1", "fr"); + page.SetCultureName("da1", "da"); + ServiceContext.ContentService.Save(page); + var versionId0 = page.VersionId; + + page.SetValue(p1.Alias, "v1fr", "fr"); + page.SetValue(p1.Alias, "v1da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId1 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr2", "fr"); + page.SetValue(p1.Alias, "v2fr", "fr"); + ServiceContext.ContentService.SaveAndPublish(page, "fr"); + var versionId2 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("da2", "da"); + page.SetValue(p1.Alias, "v2da", "da"); + ServiceContext.ContentService.SaveAndPublish(page, "da"); + var versionId3 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr3", "fr"); + page.SetCultureName("da3", "da"); + page.SetValue(p1.Alias, "v3fr", "fr"); + page.SetValue(p1.Alias, "v3da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId4 = page.VersionId; + + // now get all versions + + var versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + Assert.AreEqual(5, versions.Length); + + // current version + Assert.AreEqual(versionId4, versions[0].VersionId); + Assert.AreEqual(versionId3, versions[0].PublishedVersionId); + // published version + Assert.AreEqual(versionId3, versions[1].VersionId); + Assert.AreEqual(versionId3, versions[1].PublishedVersionId); + // previous version + Assert.AreEqual(versionId2, versions[2].VersionId); + Assert.AreEqual(versionId3, versions[2].PublishedVersionId); + // previous version + Assert.AreEqual(versionId1, versions[3].VersionId); + Assert.AreEqual(versionId3, versions[3].PublishedVersionId); + // previous version + Assert.AreEqual(versionId0, versions[4].VersionId); + Assert.AreEqual(versionId3, versions[4].PublishedVersionId); + + Assert.AreEqual("fr3", versions[4].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[3].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[2].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[1].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[0].GetPublishName("fr")); + + Assert.AreEqual("fr1", versions[4].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[3].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[2].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[1].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[0].GetCultureName("fr")); + + Assert.AreEqual("da3", versions[4].GetPublishName("da")); + Assert.AreEqual("da3", versions[3].GetPublishName("da")); + Assert.AreEqual("da3", versions[2].GetPublishName("da")); + Assert.AreEqual("da3", versions[1].GetPublishName("da")); + Assert.AreEqual("da3", versions[0].GetPublishName("da")); + + Assert.AreEqual("da1", versions[4].GetCultureName("da")); + Assert.AreEqual("da1", versions[3].GetCultureName("da")); + Assert.AreEqual("da2", versions[2].GetCultureName("da")); + Assert.AreEqual("da3", versions[1].GetCultureName("da")); + Assert.AreEqual("da3", versions[0].GetCultureName("da")); + + // all versions have the same publish infos + for (var i = 0; i < 5; i++) + { + Assert.AreEqual(versions[0].PublishDate, versions[i].PublishDate); + Assert.AreEqual(versions[0].GetPublishDate("fr"), versions[i].GetPublishDate("fr")); + Assert.AreEqual(versions[0].GetPublishDate("da"), versions[i].GetPublishDate("da")); + } + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // for all previous versions, UpdateDate is the published date + + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[3].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[3].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[2].GetUpdateDate("fr")); + Assert.AreEqual(versions[2].UpdateDate, versions[2].GetUpdateDate("da")); + + // for the published version, UpdateDate is the published date + + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("fr")); + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("da")); + Assert.AreEqual(versions[1].PublishDate, versions[1].UpdateDate); + + // for the current version, things are different + // UpdateDate is the date it was last saved + + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // so if we save again... + + page.SetCultureName("fr4", "fr"); + //page.SetCultureName("da4", "da"); + page.SetValue(p1.Alias, "v4fr", "fr"); + page.SetValue(p1.Alias, "v4da", "da"); + ServiceContext.ContentService.Save(page); + var versionId5 = page.VersionId; + + versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + // we just update the current version + Assert.AreEqual(5, versions.Length); + Assert.AreEqual(versionId4, versionId5); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + var versionsSlim = ServiceContext.ContentService.GetVersionsSlim(page.Id, 0, 50).ToArray(); + Assert.AreEqual(5, versionsSlim.Length); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versionsSlim[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versionsSlim[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versionsSlim[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // what we do in the controller to get rollback versions + var versionsSlimFr = versionsSlim.Where(x => x.UpdateDate == x.GetUpdateDate("fr")).ToArray(); + Assert.AreEqual(3, versionsSlimFr.Length); + + // alas, at the moment we do *not* properly track 'dirty' for cultures, meaning + // that we cannot synchronize dates the way we do with publish dates - and so this + // would fail - the version UpdateDate is greater than the cultures'. + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // now roll french back to its very first version + page.CopyFrom(versions[4], "fr"); // only the pure FR values + page.CopyFrom(versions[4], null); // so, must explicitly do the INVARIANT values too + page.SetCultureName(versions[4].GetPublishName("fr"), "fr"); + ServiceContext.ContentService.Save(page); + + // and voila, rolled back! + Assert.AreEqual(versions[4].GetPublishName("fr"), page.GetCultureName("fr")); + Assert.AreEqual(versions[4].GetValue(p1.Alias, "fr"), page.GetValue(p1.Alias, "fr")); + + // note that rolling back invariant values means we also rolled back... DA... at least partially + // bah? + } + [Test] public void Can_Save_Lazy_Content() { @@ -2553,6 +2758,107 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Paged_Children_WithFilterAndOrder() + { + var languageService = ServiceContext.LocalizationService; + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + var langDa = new Language("da-DK"); + + languageService.Save(langFr); + languageService.Save(langUk); + languageService.Save(langDa); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = contentTypeService.Get("umbTextpage"); + contentType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var o = new[] { 2, 1, 3, 0, 4 }; // randomly different + for (var i = 0; i < 5; i++) + { + var contentA = new Content(null, -1, contentType); + contentA.SetCultureName("contentA" + i + "uk", langUk.IsoCode); + contentA.SetCultureName("contentA" + o[i] + "fr", langFr.IsoCode); + contentA.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentA); + + var contentB = new Content(null, -1, contentType); + contentB.SetCultureName("contentB" + i + "uk", langUk.IsoCode); + contentB.SetCultureName("contentB" + o[i] + "fr", langFr.IsoCode); + contentB.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentB); + } + + // get all + var list = contentService.GetPagedChildren(-1, 0, 100, out var total).ToList(); + + Console.WriteLine("ALL"); + WriteList(list); + + // 10 items (there's already a Home content in there...) + Assert.AreEqual(11, total); + Assert.AreEqual(11, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Assert.AreEqual(0, total); + Assert.AreEqual(0, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langDa.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME da:'contentX'"); + WriteList(list); + + Assert.AreEqual(10, total); + Assert.AreEqual(10, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER ASC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + i + "fr", list[i].GetCultureName(langFr.IsoCode)); + + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", direction: Direction.Descending, culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER DESC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + (4-i) + "fr", list[i].GetCultureName(langFr.IsoCode)); + } + + private void WriteList(List list) + { + foreach (var content in list) + Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-UK"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); + Console.WriteLine("-"); + } + [Test] public void Can_SaveRead_Variations() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index bb934f1dee..00b4a32cb2 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Core.Components; namespace Umbraco.Tests.Services { @@ -24,6 +25,361 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase { + [Test] + public void Change_Content_Type_Variation_Clears_Redirects() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + ServiceContext.ContentService.Save(doc2); + + ServiceContext.RedirectUrlService.Register("hello/world", doc.Key); + ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key); + + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + //change variation + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + } + + [Test] + public void Change_Content_Type_From_Invariant_Variant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("Hello1", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the content type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello2"; + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello2", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant + + //change back property type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello3", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello3", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + } + + [Test] + public void Change_Content_Type_From_Variant_Invariant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Hello1", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("Hello1", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the content type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello2", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello2", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello3"; + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + //at this stage all property types were switched to invariant so even though the variant value + //exists it will not be returned because the property type is invariant, + //so this check proves that null will be returned + Assert.IsNull(doc.GetValue("title", "en-US")); + + //we can now switch the property type to be variant and the value can be returned again + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello3", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + } + + [Test] + public void Change_Property_Type_From_Invariant_Variant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Home"; + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change back property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + } + + [Test] + public void Change_Property_Type_From_Variant_Invariant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + } + + [Test] + public void Change_Property_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.AreEqual("hello world", doc2.GetValue("title", "en-US")); + } + + [Test] + public void Change_Content_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the content type to be invariant + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back content type to be variant + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + //this will be null because the doc type was changed back to variant but it's property types don't get changed back + Assert.IsNull(doc.GetValue("title", "en-US")); + Assert.IsNull(doc2.GetValue("title", "en-US")); + } + [Test] public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin() { diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index ea6dd8248e..13cde1c659 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -293,6 +293,29 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, membersInRole.Count()); } + [Test] + public void Associate_Members_To_Roles_With_Member_Id_Casing() + { + ServiceContext.MemberService.AddRole("MyTestRole1"); + + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var member1 = MockedMember.CreateSimpleMember(memberType, "test1", "test1@test.com", "pass", "test1"); + ServiceContext.MemberService.Save(member1); + var member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2"); + ServiceContext.MemberService.Save(member2); + + // temp make sure they exist + Assert.IsNotNull(ServiceContext.MemberService.GetById(member1.Id)); + Assert.IsNotNull(ServiceContext.MemberService.GetById(member2.Id)); + + ServiceContext.MemberService.AssignRoles(new[] { member1.Id, member2.Id }, new[] { "mytestrole1" }); + + var membersInRole = ServiceContext.MemberService.GetMembersInRole("MyTestRole1"); + + Assert.AreEqual(2, membersInRole.Count()); + } + [Test] public void Associate_Members_To_Roles_With_Member_Username() { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 8ad318faa5..d0dfcc8c2e 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -58,6 +58,8 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; + protected ISqlContext SqlContext => Container.GetInstance(); + public override void SetUp() { base.SetUp(); diff --git a/src/Umbraco.Tests/UI/LegacyDialogTests.cs b/src/Umbraco.Tests/UI/LegacyDialogTests.cs index ba7c4f0e66..99391104ab 100644 --- a/src/Umbraco.Tests/UI/LegacyDialogTests.cs +++ b/src/Umbraco.Tests/UI/LegacyDialogTests.cs @@ -23,8 +23,6 @@ namespace Umbraco.Tests.UI } } - [TestCase(typeof(MemberGroupTasks), Constants.Applications.Members)] - [TestCase(typeof(dictionaryTasks), Constants.Applications.Settings)] [TestCase(typeof(macroTasks), Constants.Applications.Packages)] [TestCase(typeof(CreatedPackageTasks), Constants.Applications.Packages)] public void Check_Assigned_Apps_For_Tasks(Type taskType, string app) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e11695bef5..864e971f25 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -120,10 +120,12 @@ + + @@ -392,7 +394,6 @@ - diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index db496dbfd3..a57843ac3e 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,6 +1,6 @@ using System; +using System.Linq; using System.Web; -using HtmlAgilityPack; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -10,6 +10,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; @@ -60,14 +61,6 @@ namespace Umbraco.Tests.Web [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] //this one has an invalid char so won't match [TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] - // with a-tag with data-udi attribute, that needs to be stripped - [TestCase("hello world ", "hello world ")] - // with a-tag with data-udi attribute spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id, that needs to be strippde - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] public void ParseLocalLinks(string input, string result) { var serviceCtxMock = new TestObjects(null).GetServiceContextMock(); @@ -108,7 +101,7 @@ namespace Umbraco.Tests.Web //setup a quick mock of the WebRouting section Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), //pass in the custom url provider - new[] { testUrlProvider.Object }, + new[]{ testUrlProvider.Object }, globalSettings, new TestVariationContextAccessor(), true)) @@ -118,27 +111,5 @@ namespace Umbraco.Tests.Web Assert.AreEqual(result, output); } } - - [Test] - public void StripDataUdiAttributesUsingSrtringOnLinks() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } - - [Test] - public void StripDataUdiAttributesUsingStringOnImages() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } } } diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json deleted file mode 100644 index 9f9c4fff01..0000000000 --- a/src/Umbraco.Web.UI.Client/bower.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "name": "umbraco", - "version": "7", - "homepage": "https://github.com/umbraco/Umbraco-CMS", - "authors": [ - "Shannon " - ], - "description": "Umbraco CMS", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "angular": "~1.7.4", - "angular-cookies": "~1.7.4", - "angular-sanitize": "~1.7.4", - "angular-touch": "~1.7.4", - "angular-route": "~1.7.4", - "angular-animate": "~1.7.4", - "angular-i18n": "~1.7.4", - "signalr": "^2.2.1", - "typeahead.js": "~0.10.5", - "underscore": "~1.9.1", - "rgrove-lazyload": "*", - "bootstrap-social": "~4.8.0", - "jquery": "2.2.4", - "jquery-ui": "~1.12.0", - "jquery-migrate": "1.4.0", - "jquery-validate": "~1.17.0", - "jquery-validation-unobtrusive": "3.2.10", - "angular-dynamic-locale": "~0.1.36", - "ng-file-upload": "~12.2.13", - "tinymce": "~4.7.1", - "codemirror": "~5.3.0", - "angular-local-storage": "~0.7.1", - "moment": "~2.10.3", - "ace-builds": "~1.3.0", - "clipboard": "~2.0.0", - "font-awesome": "~4.2", - "animejs": "^2.2.0", - "angular-ui-sortable": "0.14.4", - "angular-messages": "^1.7.2", - "jsdiff": "^3.4.0" - }, - "install": { - "path": "lib-bower", - "ignore": [ - "font-awesome", - "bootstrap", - "codemirror", - "ace-builds" - ], - "sources": { - "moment": [ - "bower_components/moment/min/moment.min.js", - "bower_components/moment/min/moment-with-locales.js", - "bower_components/moment/min/moment-with-locales.min.js", - "bower_components/moment/locale/*.js" - ], - "underscore": [ - "bower_components/underscore/underscore-min.js", - "bower_components/underscore/underscore-min.map" - ], - "jquery": [ - "bower_components/jquery/dist/jquery.min.js", - "bower_components/jquery/dist/jquery.min.map" - ], - "angular-dynamic-locale": [ - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" - ], - "angular-local-storage": [ - "bower_components/angular-local-storage/dist/angular-local-storage.min.js", - "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" - ], - "tinymce": [ - "bower_components/tinymce/tinymce.min.js" - ], - "angular-i18n": "bower_components/angular-i18n/angular-locale_*.js", - "typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js", - "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", - "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", - "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", - "jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js", - "clipboard": "bower_components/clipboard/dist/clipboard.min.js", - "animejs": "bower_components/animejs/anime.min.js", - "jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js", - "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js", - "jsdiff": "bower_components/jsdiff/diff.min.js" - } - }, - "devDependencies": { - "angular-mocks": "~1.7.2" - } -} diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 7a6d909154..d1bbfe65db 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -6,21 +6,22 @@ var wrap = require("gulp-wrap-js"); var sort = require('gulp-sort'); var connect = require('gulp-connect'); var open = require('gulp-open'); -const babel = require("gulp-babel"); +var babel = require("gulp-babel"); var runSequence = require('run-sequence'); -const imagemin = require('gulp-imagemin'); +var imagemin = require('gulp-imagemin'); var _ = require('lodash'); var MergeStream = require('merge-stream'); // js -const eslint = require('gulp-eslint'); +var eslint = require('gulp-eslint'); //Less + css var postcss = require('gulp-postcss'); var less = require('gulp-less'); var autoprefixer = require('autoprefixer'); var cssnano = require('cssnano'); +var cleanCss = require("gulp-clean-css"); // Documentation var gulpDocs = require('gulp-ngdocs'); @@ -32,7 +33,7 @@ var karmaServer = require('karma').Server; Helper functions ***************************************************************/ function processJs(files, out) { - + return gulp.src(files) // check for js errors .pipe(eslint()) @@ -49,18 +50,18 @@ function processJs(files, out) { } function processLess(files, out) { - var processors = [ autoprefixer, - cssnano({zindex: false}), + cssnano({zindex: false}) ]; return gulp.src(files) .pipe(less()) + .pipe(cleanCss()) .pipe(postcss(processors)) .pipe(rename(out)) .pipe(gulp.dest(root + targets.css)); - + console.log(out + " compiled"); } @@ -69,7 +70,7 @@ Paths and destinations Each group is iterated automatically in the setup tasks below ***************************************************************/ var sources = { - + //less files used by backoffice and preview //processed in the less task less: { @@ -105,8 +106,7 @@ var sources = { less: "./src/less/**/*.less", js: "./src/*.js", lib: "./lib/**/*", - bower: "./lib-bower/**/*", - assets: "./src/assets/**" + assets: "./src/assets/**" } }; @@ -140,92 +140,256 @@ gulp.task('docserve', function(cb) { }); /************************** - * Task processes and copies all dependencies, either installed by bower, npm or stored locally in the project + * Task processes and copies all dependencies, either installed by npm or stored locally in the project **************************/ -gulp.task('dependencies', function () { - - //bower component/npm specific copy rules - //this is to patch the sometimes wonky rules these libs are distrbuted under +gulp.task('dependencies', function () { //as we do multiple things in this task, we merge the multiple streams var stream = new MergeStream(); - //Tinymce plugins/themes - stream.add( - gulp.src(["./bower_components/tinymce/plugins/**", - "./bower_components/tinymce/themes/**"], - { base: "./bower_components/tinymce/" }) - .pipe(gulp.dest(root + targets.lib + "/tinymce")) - ); + // Pick the dependencies we need from each package + // so we don't just ship with a lot of files that aren't needed + const nodeModules = [ + { + "name": "ace-builds", + "src": [ + "./node_modules/ace-builds/src-min-noconflict/ace.js", + "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", + "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", + "./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/text.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", + "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", + "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", + "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", + "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js" + ], + "base": "./node_modules/ace-builds" + }, + { + "name": "angular", + "src": ["./node_modules/angular/angular.js"], + "base": "./node_modules/angular" + }, + { + "name": "angular-cookies", + "src": ["./node_modules/angular-cookies/angular-cookies.js"], + "base": "./node_modules/angular-cookies" + }, + { + "name": "angular-dynamic-locale", + "src": [ + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" + ], + "base": "./node_modules/angular-dynamic-locale/dist" + }, + { + "name": "angular-sanitize", + "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], + "base": "./node_modules/angular-sanitize" + }, + { + "name": "angular-touch", + "src": ["./node_modules/angular-touch/angular-touch.js"], + "base": "./node_modules/angular-touch" + }, + { + "name": "angular-ui-sortable", + "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], + "base": "./node_modules/angular-ui-sortable/dist" + }, + { + "name": "angular-route", + "src": ["./node_modules/angular-route/angular-route.js"], + "base": "./node_modules/angular-route" + }, + { + "name": "angular-animate", + "src": ["./node_modules/angular-animate/angular-animate.js"], + "base": "./node_modules/angular-animate" + }, + { + "name": "angular-i18n", + "src": [ + "./node_modules/angular-i18n/angular-i18n.js", + "./node_modules/angular-i18n/angular-locale_*.js" + ], + "base": "./node_modules/angular-i18n" + }, + { + "name": "angular-local-storage", + "src": [ + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" + ], + "base": "./node_modules/angular-local-storage/dist" + }, + { + "name": "angular-messages", + "src": ["./node_modules/angular-messages/angular-messages.js"], + "base": "./node_modules/angular-messages" + }, + { + "name": "angular-mocks", + "src": ["./node_modules/angular-mocks/angular-mocks.js"], + "base": "./node_modules/angular-mocks" + }, + { + "name": "animejs", + "src": ["./node_modules/animejs/anime.min.js"], + "base": "./node_modules/animejs" + }, + { + "name": "bootstrap-social", + "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], + "base": "./node_modules/bootstrap-social" + }, + { + "name": "clipboard", + "src": ["./node_modules/clipboard/dist/clipboard.min.js"], + "base": "./node_modules/clipboard/dist" + }, + { + "name": "codemirror", + "src": [ + "./node_modules/codemirror/lib/codemirror.js", + "./node_modules/codemirror/lib/codemirror.css", - //font-awesome - stream.add( - gulp.src(["./bower_components/font-awesome/fonts/*", - "./bower_components/font-awesome/css/font-awesome.min.css"], - { base: "./bower_components/font-awesome/" }) - .pipe(gulp.dest(root + targets.lib + "/font-awesome")) - ); - - // ace Editor - stream.add( - gulp.src(["bower_components/ace-builds/src-min-noconflict/ace.js", - "bower_components/ace-builds/src-min-noconflict/ext-language_tools.js", - "bower_components/ace-builds/src-min-noconflict/ext-searchbox.js", - "bower_components/ace-builds/src-min-noconflict/ext-settings_menu.js", - "bower_components/ace-builds/src-min-noconflict/snippets/text.js", - "bower_components/ace-builds/src-min-noconflict/snippets/javascript.js", - "bower_components/ace-builds/src-min-noconflict/theme-chrome.js", - "bower_components/ace-builds/src-min-noconflict/mode-razor.js", - "bower_components/ace-builds/src-min-noconflict/mode-javascript.js", - "bower_components/ace-builds/src-min-noconflict/worker-javascript.js"], - { base: "./bower_components/ace-builds/" }) - .pipe(gulp.dest(root + targets.lib + "/ace-builds")) - ); + "./node_modules/codemirror/mode/css/css.js", + "./node_modules/codemirror/mode/javascript/javascript.js", + "./node_modules/codemirror/mode/xml/xml.js", + "./node_modules/codemirror/mode/htmlmixed/htmlmixed.js", - // code mirror - stream.add( - gulp.src([ - "bower_components/codemirror/lib/codemirror.js", - "bower_components/codemirror/lib/codemirror.css", + "./node_modules/codemirror/addon/search/*", + "./node_modules/codemirror/addon/edit/*", + "./node_modules/codemirror/addon/selection/*", + "./node_modules/codemirror/addon/dialog/*" + ], + "base": "./node_modules/codemirror" + }, + { + "name": "jsdiff", + "src": ["./node_modules/diff/dist/diff.min.js"], + "base": "./node_modules/diff/dist" + }, + { + "name": "flatpickr", + "src": [ + "./node_modules/flatpickr/dist/flatpickr.js", + "./node_modules/flatpickr/dist/flatpickr.css" + ], + "base": "./node_modules/flatpickr/dist" + }, + { + "name": "font-awesome", + "src": [ + "./node_modules/font-awesome/fonts/*", + "./node_modules/font-awesome/css/font-awesome.min.css" + ], + "base": "./node_modules/font-awesome" + }, + { + "name": "jquery", + "src": [ + "./node_modules/jquery/dist/jquery.min.js", + "./node_modules/jquery/dist/jquery.min.map" + ], + "base": "./node_modules/jquery/dist" + }, + { + "name": "jquery-migrate", + "src": ["./node_modules/jquery-migrate/dist/jquery-migrate.min.js"], + "base": "./node_modules/jquery-migrate/dist" + }, + { + "name": "jquery-ui", + "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], + "base": "./node_modules/jquery-ui-dist" + }, + { + "name": "jquery-validate", + "src": ["./node_modules/jquery-validation/dist/jquery.validate.min.js"], + "base": "./node_modules/jquery-validation/dist" + }, + { + "name": "jquery-validation-unobtrusive", + "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], + "base": "./node_modules/jquery-validation-unobtrusive/dist" + }, + { + "name": "lazyload-js", + "src": ["./node_modules/lazyload-js/lazyload.min.js"], + "base": "./node_modules/lazyload-js" + }, + // TODO: We can optimize here: + // we don't have to ship with the moment-with-locales libraries + // we lazyload the user locale + { + "name": "moment", + "src": [ + "./node_modules/moment/min/moment.min.js", + "./node_modules/moment/min/moment-with-locales.js", + "./node_modules/moment/min/moment-with-locales.min.js" + ], + "base": "./node_modules/moment/min" + }, + { + "name": "moment", + "src": [ + "./node_modules/moment/locale/*.js" + ], + "base": "./node_modules/moment/locale" + }, + { + "name": "ng-file-upload", + "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], + "base": "./node_modules/ng-file-upload/dist" + }, + { + "name": "signalr", + "src": ["./node_modules/signalr/jquery.signalR.js"], + "base": "./node_modules/signalr" + }, + { + "name": "tinymce", + "src": [ + "./node_modules/tinymce/tinymce.min.js", + "./node_modules/tinymce/plugins/**", + "./node_modules/tinymce/themes/**" + ], + "base": "./node_modules/tinymce" + }, + { + "name": "typeahead.js", + "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], + "base": "./node_modules/typeahead.js/dist" + }, + { + "name": "underscore", + "src": ["node_modules/underscore/underscore-min.js"], + "base": "./node_modules/underscore" + } + ]; - "bower_components/codemirror/mode/css/*", - "bower_components/codemirror/mode/javascript/*", - "bower_components/codemirror/mode/xml/*", - "bower_components/codemirror/mode/htmlmixed/*", + // add streams for node modules + nodeModules.forEach(module => { + stream.add( + gulp.src(module.src, + { base: module.base }) + .pipe(gulp.dest(root + targets.lib + "/" + module.name)) + ); + }); - "bower_components/codemirror/addon/search/*", - "bower_components/codemirror/addon/edit/*", - "bower_components/codemirror/addon/selection/*", - "bower_components/codemirror/addon/dialog/*"], - { base: "./bower_components/codemirror/" }) - .pipe(gulp.dest(root + targets.lib + "/codemirror")) - ); - - // npm dependencies - // flatpickr - stream.add( - gulp.src([ - "./node_modules/flatpickr/dist/flatpickr.js", - "./node_modules/flatpickr/dist/flatpickr.css"], - { base: "./node_modules/flatpickr/dist" }) - .pipe(gulp.dest(root + targets.lib + "/flatpickr")) - ); - - //copy over libs which are not on bower (/lib) and - //libraries that have been managed by bower-installer (/lib-bower) + //copy over libs which are not on npm (/lib) stream.add( gulp.src(sources.globs.lib) .pipe(gulp.dest(root + targets.lib)) ); - stream.add( - gulp.src(sources.globs.bower) - .pipe(gulp.dest(root + targets.lib)) - ); - - //Copies all static assets into /root / assets folder + //Copies all static assets into /root / assets folder //css, fonts and image files - stream.add( + stream.add( gulp.src(sources.globs.assets) .pipe(imagemin([ imagemin.gifsicle({interlaced: true}), @@ -243,20 +407,20 @@ gulp.task('dependencies', function () { // Copies all the less files related to the preview into their folder //these are not pre-processed as preview has its own less combiler client side - stream.add( + stream.add( gulp.src("src/canvasdesigner/editors/*.less") .pipe(gulp.dest(root + targets.assets + "/less")) ); - + // Todo: check if we need these fileSize - stream.add( + stream.add( gulp.src("src/views/propertyeditors/grid/config/*.*") .pipe(gulp.dest(root + targets.views + "/propertyeditors/grid/config")) - ); - stream.add( + ); + stream.add( gulp.src("src/views/dashboard/default/*.jpg") .pipe(gulp.dest(root + targets.views + "/dashboard/default")) - ); + ); return stream; }); @@ -265,8 +429,8 @@ gulp.task('dependencies', function () { /************************** * Copies all angular JS files into their seperate umbraco.*.js file **************************/ -gulp.task('js', function () { - +gulp.task('js', function () { + //we run multiple streams, so merge them all together var stream = new MergeStream(); @@ -283,7 +447,7 @@ gulp.task('js', function () { }); gulp.task('less', function () { - + var stream = new MergeStream(); _.forEach(sources.less, function (group) { @@ -306,9 +470,9 @@ gulp.task('views', function () { gulp.src(group.files) .pipe( gulp.dest(root + targets.views + group.folder) ) ); - + }); - + return stream; }); @@ -323,13 +487,13 @@ gulp.task('watch', function () { if(group.watch !== false){ - stream.add( + stream.add( watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) { console.info(file.path + " has changed, added to: " + group.out); processJs(group.files, group.out); - + }) ); @@ -338,7 +502,7 @@ gulp.task('watch', function () { }); - stream.add( + stream.add( //watch all less files and trigger the less task watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () { gulp.run(['less']); @@ -346,13 +510,13 @@ gulp.task('watch', function () { ); //watch all views - copy single file changes - stream.add( + stream.add( watch(sources.globs.views, { interval: watchInterval }) .pipe(gulp.dest(root + targets.views)) ); //watch all app js files that will not be merged - copy single file changes - stream.add( + stream.add( watch(sources.globs.js, { interval: watchInterval }) .pipe(gulp.dest(root + targets.js)) ); @@ -397,7 +561,7 @@ gulp.task('connect:docs', function (cb) { }); gulp.task('open:docs', function (cb) { - + var options = { uri: 'http://localhost:8880/index.html' }; diff --git a/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js b/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js deleted file mode 100644 index 9903359454..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js +++ /dev/null @@ -1,5495 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * emk - * Daniel Glazman - * L. David Baron - * Boris Zbarsky - * Mats Palmgren - * Christian Biesinger - * Jeff Walden - * Jonathon Jongsma , Collabora Ltd. - * Siraj Razick , Collabora Ltd. - * Daniel Glazman - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -var kCHARSET_RULE_MISSING_SEMICOLON = "Missing semicolon at the end of @charset rule"; -var kCHARSET_RULE_CHARSET_IS_STRING = "The charset in the @charset rule should be a string"; -var kCHARSET_RULE_MISSING_WS = "Missing mandatory whitespace after @charset"; -var kIMPORT_RULE_MISSING_URL = "Missing URL in @import rule"; -var kURL_EOF = "Unexpected end of stylesheet"; -var kURL_WS_INSIDE = "Multiple tokens inside a url() notation"; -var kVARIABLES_RULE_POSITION = "@variables rule invalid at this position in the stylesheet"; -var kIMPORT_RULE_POSITION = "@import rule invalid at this position in the stylesheet"; -var kNAMESPACE_RULE_POSITION = "@namespace rule invalid at this position in the stylesheet"; -var kCHARSET_RULE_CHARSET_SOF = "@charset rule invalid at this position in the stylesheet"; -var kUNKNOWN_AT_RULE = "Unknow @-rule"; - -/* FROM http://peter.sh/data/vendor-prefixed-css.php?js=1 */ - -var kCSS_VENDOR_VALUES = { - "-moz-box": {"webkit": "-webkit-box", "presto": "", "trident": "", "generic": "box" }, - "-moz-inline-box": {"webkit": "-webkit-inline-box", "presto": "", "trident": "", "generic": "inline-box" }, - "-moz-initial": {"webkit": "", "presto": "", "trident": "", "generic": "initial" }, - "flex": {"webkit": "-webkit-flex", "presto": "", "trident": "", "generic": "" }, - "inline-flex": {"webkit": "-webkit-inline-flex", "presto": "", "trident": "", "generic": "" }, - - "linear-gradient": {"webkit20110101":FilterLinearGradient, - "webkit": FilterLinearGradient, - "presto": FilterLinearGradient, - "trident": FilterLinearGradient, - "gecko1.9.2": FilterLinearGradient }, - "repeating-linear-gradient": {"webkit20110101":FilterLinearGradient, - "webkit": FilterLinearGradient, - "presto": FilterLinearGradient, - "trident": FilterLinearGradient, - "gecko1.9.2": FilterLinearGradient }, - - "radial-gradient": {"webkit20110101":FilterRadialGradient, - "webkit": FilterRadialGradient, - "presto": FilterRadialGradient, - "trident": FilterRadialGradient, - "gecko1.9.2": FilterRadialGradient }, - "repeating-radial-gradient": {"webkit20110101":FilterRadialGradient, - "webkit": FilterRadialGradient, - "presto": FilterRadialGradient, - "trident": FilterRadialGradient, - "gecko1.9.2": FilterRadialGradient } -}; - -var kCSS_PREFIXED_VALUE = [ - {"gecko": "-moz-box", "webkit": "-moz-box", "presto": "", "trident": "", "generic": "box"} -]; - -var kCSS_VENDOR_PREFIXES = -{"lastUpdate":1374677405,"properties":[ -{"gecko":"","webkit":"","presto":"","trident":"-ms-accelerator","status":"P"}, -{"gecko":"","webkit":"","presto":"-wap-accesskey","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-align-content","presto":"","trident":"","status":""}, -{"gecko":"align-items","webkit":"-webkit-align-items","presto":"","trident":"","status":""}, -{"gecko":"align-self","webkit":"-webkit-align-self","presto":"","trident":"","status":""}, -{"gecko":"animation","webkit":"-webkit-animation","presto":"","trident":"animation","status":"WD"}, -{"gecko":"animation-delay","webkit":"-webkit-animation-delay","presto":"","trident":"animation-delay","status":"WD"}, -{"gecko":"animation-direction","webkit":"-webkit-animation-direction","presto":"","trident":"animation-direction","status":"WD"}, -{"gecko":"animation-duration","webkit":"-webkit-animation-duration","presto":"","trident":"animation-duration","status":"WD"}, -{"gecko":"animation-fill-mode","webkit":"-webkit-animation-fill-mode","presto":"","trident":"animation-fill-mode","status":"ED"}, -{"gecko":"animation-iteration-count","webkit":"-webkit-animation-iteration-count","presto":"","trident":"animation-iteration-count","status":"WD"}, -{"gecko":"animation-name","webkit":"-webkit-animation-name","presto":"","trident":"animation-name","status":"WD"}, -{"gecko":"animation-play-state","webkit":"-webkit-animation-play-state","presto":"","trident":"animation-play-state","status":"WD"}, -{"gecko":"animation-timing-function","webkit":"-webkit-animation-timing-function","presto":"","trident":"animation-timing-function","status":"WD"}, -{"gecko":"","webkit":"-webkit-app-region","presto":"","trident":"","status":""}, -{"gecko":"-moz-appearance","webkit":"-webkit-appearance","presto":"","trident":"","status":"CR"}, -{"gecko":"","webkit":"-webkit-aspect-ratio","presto":"","trident":"","status":""}, -{"gecko":"backface-visibility","webkit":"-webkit-backface-visibility","presto":"","trident":"backface-visibility","status":"WD"}, -{"gecko":"","webkit":"-webkit-background-blend-mode","presto":"","trident":"","status":""}, -{"gecko":"background-clip","webkit":"-webkit-background-clip","presto":"background-clip","trident":"background-clip","status":"WD"}, -{"gecko":"","webkit":"-webkit-background-composite","presto":"","trident":"","status":""}, -{"gecko":"-moz-background-inline-policy","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"background-origin","webkit":"-webkit-background-origin","presto":"background-origin","trident":"background-origin","status":"WD"}, -{"gecko":"","webkit":"background-position-x","presto":"","trident":"-ms-background-position-x","status":""}, -{"gecko":"","webkit":"background-position-y","presto":"","trident":"-ms-background-position-y","status":""}, -{"gecko":"background-size","webkit":"-webkit-background-size","presto":"background-size","trident":"background-size","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-behavior","status":""}, -{"gecko":"-moz-binding","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-blend-mode","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-block-progression","status":""}, -{"gecko":"","webkit":"-webkit-border-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-color","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-style","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-color","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-style","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-width","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-bottom-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-bottom-left-radius","webkit":"-webkit-border-bottom-left-radius","presto":"border-bottom-left-radius","trident":"border-bottom-left-radius","status":"WD"}, -{"gecko":"border-bottom-right-radius","webkit":"-webkit-border-bottom-right-radius","presto":"border-bottom-right-radius","trident":"border-bottom-right-radius","status":"WD"}, -{"gecko":"-moz-border-end","webkit":"-webkit-border-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-color","webkit":"-webkit-border-end-color","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-style","webkit":"-webkit-border-end-style","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-width","webkit":"-webkit-border-end-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-fit","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-border-horizontal-spacing","presto":"","trident":"","status":""}, -{"gecko":"border-image","webkit":"-webkit-border-image","presto":"-o-border-image","trident":"","status":"WD"}, -{"gecko":"-moz-border-left-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-radius","webkit":"-webkit-border-radius","presto":"border-radius","trident":"border-radius","status":"WD"}, -{"gecko":"-moz-border-right-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-border-start","webkit":"-webkit-border-start","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-color","webkit":"-webkit-border-start-color","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-style","webkit":"-webkit-border-start-style","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-width","webkit":"-webkit-border-start-width","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-top-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-top-left-radius","webkit":"-webkit-border-top-left-radius","presto":"border-top-left-radius","trident":"border-top-left-radius","status":"WD"}, -{"gecko":"border-top-right-radius","webkit":"-webkit-border-top-right-radius","presto":"border-top-right-radius","trident":"border-top-right-radius","status":"WD"}, -{"gecko":"","webkit":"-webkit-border-vertical-spacing","presto":"","trident":"","status":""}, -{"gecko":"-moz-box-align","webkit":"-webkit-box-align","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-decoration-break","presto":"box-decoration-break","trident":"","status":"WD"}, -{"gecko":"-moz-box-direction","webkit":"-webkit-box-direction","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-flex","webkit":"-webkit-box-flex","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-flex-group","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-lines","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-ordinal-group","webkit":"-webkit-box-ordinal-group","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-orient","webkit":"-webkit-box-orient","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-pack","webkit":"-webkit-box-pack","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-reflect","presto":"","trident":"","status":""}, -{"gecko":"box-shadow","webkit":"-webkit-box-shadow","presto":"box-shadow","trident":"box-shadow","status":"WD"}, -{"gecko":"-moz-box-sizing","webkit":"-webkit-box-sizing","presto":"box-sizing","trident":"box-sizing","status":"CR"}, -{"gecko":"caption-side","webkit":"-epub-caption-side","presto":"caption-side","trident":"caption-side","status":""}, -{"gecko":"clip-path","webkit":"-webkit-clip-path","presto":"clip-path","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-color-correction","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-axis","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-inside","presto":"","trident":"","status":""}, -{"gecko":"-moz-column-count","webkit":"-webkit-column-count","presto":"column-count","trident":"column-count","status":"CR"}, -{"gecko":"-moz-column-fill","webkit":"","presto":"column-fill","trident":"column-fill","status":"CR"}, -{"gecko":"-moz-column-gap","webkit":"-webkit-column-gap","presto":"column-gap","trident":"column-gap","status":"CR"}, -{"gecko":"","webkit":"-webkit-column-progression","presto":"","trident":"","status":""}, -{"gecko":"-moz-column-rule","webkit":"-webkit-column-rule","presto":"column-rule","trident":"column-rule","status":"CR"}, -{"gecko":"-moz-column-rule-color","webkit":"-webkit-column-rule-color","presto":"column-rule-color","trident":"column-rule-color","status":"CR"}, -{"gecko":"-moz-column-rule-style","webkit":"-webkit-column-rule-style","presto":"column-rule-style","trident":"column-rule-style","status":"CR"}, -{"gecko":"-moz-column-rule-width","webkit":"-webkit-column-rule-width","presto":"column-rule-width","trident":"column-rule-width","status":"CR"}, -{"gecko":"","webkit":"-webkit-column-span","presto":"column-span","trident":"column-span","status":"CR"}, -{"gecko":"-moz-column-width","webkit":"-webkit-column-width","presto":"column-width","trident":"column-width","status":"CR"}, -{"gecko":"-moz-columns","webkit":"-webkit-columns","presto":"columns","trident":"columns","status":"CR"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-chaining","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-points","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-type","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zooming","status":""}, -{"gecko":"","webkit":"-webkit-cursor-visibility","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-dashboard-region","presto":"-apple-dashboard-region","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-device-pixel-ratio","trident":"","status":""}, -{"gecko":"filter","webkit":"-webkit-filter","presto":"filter","trident":"-ms-filter","status":""}, -{"gecko":"flex","webkit":"-webkit-flex","presto":"","trident":"-ms-flex","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-align","status":""}, -{"gecko":"flex-basis","webkit":"-webkit-flex-basis","presto":"","trident":"","status":""}, -{"gecko":"flex-direction","webkit":"-webkit-flex-direction","presto":"","trident":"-ms-flex-direction","status":""}, -{"gecko":"","webkit":"-webkit-flex-flow","presto":"","trident":"","status":""}, -{"gecko":"flex-grow","webkit":"-webkit-flex-grow","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-order","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-pack","status":""}, -{"gecko":"flex-shrink","webkit":"-webkit-flex-shrink","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-flex-wrap","presto":"","trident":"-ms-flex-wrap","status":""}, -{"gecko":"-moz-float-edge","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-flow-from","presto":"","trident":"-ms-flow-from","status":""}, -{"gecko":"","webkit":"-webkit-flow-into","presto":"","trident":"-ms-flow-into","status":""}, -{"gecko":"","webkit":"","presto":"-o-focus-opacity","trident":"","status":""}, -{"gecko":"-moz-font-feature-settings","webkit":"-webkit-font-feature-settings","presto":"","trident":"font-feature-settings","status":""}, -{"gecko":"font-kerning","webkit":"-webkit-font-kerning","presto":"","trident":"","status":""}, -{"gecko":"-moz-font-language-override","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-font-size-delta","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-font-smoothing","presto":"","trident":"","status":""}, -{"gecko":"font-variant-ligatures","webkit":"-webkit-font-variant-ligatures","presto":"","trident":"","status":""}, -{"gecko":"-moz-force-broken-image-icon","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-columns","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-flow","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-rows","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-column","presto":"","trident":"-ms-grid-column","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-align","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-span","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-columns","status":"WD"}, -{"gecko":"","webkit":"-webkit-grid-definition-columns","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-definition-rows","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-end","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-row","presto":"","trident":"-ms-grid-row","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-align","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-span","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-rows","status":"WD"}, -{"gecko":"","webkit":"-webkit-grid-start","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-high-contrast-adjust","status":""}, -{"gecko":"","webkit":"-webkit-highlight","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-character","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-chars","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-lines","presto":"","trident":"-ms-hyphenate-limit-lines","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-zone","status":""}, -{"gecko":"-moz-hyphens","webkit":"-epub-hyphens","presto":"","trident":"-ms-hyphens","status":"WD"}, -{"gecko":"-moz-image-region","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"ime-mode","webkit":"","presto":"","trident":"-ms-ime-mode","status":""}, -{"gecko":"","webkit":"","presto":"-wap-input-format","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-input-required","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-interpolation-mode","status":""}, -{"gecko":"","webkit":"","presto":"-xv-interpret-as","trident":"","status":""}, -{"gecko":"justify-content","webkit":"-webkit-justify-content","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-flow","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-char","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-line","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-mode","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-type","status":""}, -{"gecko":"","webkit":"-webkit-line-align","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-box-contain","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-break","presto":"","trident":"line-break","status":""}, -{"gecko":"","webkit":"-webkit-line-clamp","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-grid","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-snap","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-link","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-link-source","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-locale","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-logical-height","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-after-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-before","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-before-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-bottom-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-collapse","presto":"","trident":"","status":""}, -{"gecko":"-moz-margin-end","webkit":"-webkit-margin-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-margin-start","webkit":"-webkit-margin-start","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-top-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-marquee-dir","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee-direction","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-increment","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-marquee-loop","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-repetition","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee-speed","presto":"-wap-marquee-speed","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-style","presto":"-wap-marquee-style","trident":"","status":"WD"}, -{"gecko":"mask","webkit":"-webkit-mask","presto":"mask","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-outset","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-repeat","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-slice","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-source","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-width","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-clip","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-composite","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-image","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-origin","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-size","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-max-logical-height","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-max-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-min-logical-height","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-min-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"","presto":"-o-mini-fold","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-nbsp-mode","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"","presto":"-o-object-fit","trident":"","status":"ED"}, -{"gecko":"","webkit":"","presto":"-o-object-position","trident":"","status":"ED"}, -{"gecko":"opacity","webkit":"-webkit-opacity","presto":"opacity","trident":"opacity","status":"WD"}, -{"gecko":"order","webkit":"-webkit-order","presto":"","trident":"","status":""}, -{"gecko":"-moz-orient","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-outline-radius","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-bottomleft","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-bottomright","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-topleft","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-topright","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-overflow-scrolling","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-overflow-style","status":"CR"}, -{"gecko":"overflow-x","webkit":"overflow-x","presto":"overflow-x","trident":"-ms-overflow-x","status":"WD"}, -{"gecko":"overflow-y","webkit":"overflow-y","presto":"overflow-y","trident":"-ms-overflow-y","status":"WD"}, -{"gecko":"","webkit":"-webkit-padding-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-padding-before","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-padding-end","webkit":"-webkit-padding-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-padding-start","webkit":"-webkit-padding-start","presto":"","trident":"","status":"ED"}, -{"gecko":"perspective","webkit":"-webkit-perspective","presto":"","trident":"perspective","status":"WD"}, -{"gecko":"perspective-origin","webkit":"-webkit-perspective-origin","presto":"","trident":"perspective-origin","status":"WD"}, -{"gecko":"","webkit":"-webkit-perspective-origin-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-perspective-origin-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-phonemes","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-print-color-adjust","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-progress-appearance","status":""}, -{"gecko":"","webkit":"-webkit-region-break-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-break-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-break-inside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-fragment","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-rtl-ordering","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-ruby-position","presto":"","trident":"ruby-position","status":"CR"}, -{"gecko":"-moz-script-level","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-script-min-size","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-script-size-multiplier","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-chaining","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-rails","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-x","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-y","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-type","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-x","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-y","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-translation","status":""}, -{"gecko":"","webkit":"","presto":"scrollbar-arrow-color","trident":"-ms-scrollbar-arrow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-base-color","trident":"-ms-scrollbar-base-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-darkshadow-color","trident":"-ms-scrollbar-darkshadow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-face-color","trident":"-ms-scrollbar-face-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-highlight-color","trident":"-ms-scrollbar-highlight-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-shadow-color","trident":"-ms-scrollbar-shadow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-track-color","trident":"-ms-scrollbar-track-color","status":"P"}, -{"gecko":"","webkit":"-webkit-shape-inside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-margin","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-outside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-padding","presto":"","trident":"","status":""}, -{"gecko":"-moz-stack-sizing","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-svg-shadow","presto":"","trident":"","status":""}, -{"gecko":"-moz-tab-size","webkit":"tab-size","presto":"-o-tab-size","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-table-baseline","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-tap-highlight-color","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-text-align-last","webkit":"-webkit-text-align-last","presto":"","trident":"-ms-text-align-last","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-text-autospace","status":"WD"}, -{"gecko":"-moz-text-blink","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-combine","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-color","webkit":"-webkit-text-decoration-color","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-line","webkit":"-webkit-text-decoration-line","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-style","webkit":"-webkit-text-decoration-style","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-decorations-in-effect","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis-color","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-emphasis-position","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis-style","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-fill-color","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-justify","presto":"","trident":"-ms-text-justify","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-text-kashida-space","status":"P"}, -{"gecko":"","webkit":"-epub-text-orientation","presto":"","trident":"","status":""}, -{"gecko":"text-overflow","webkit":"text-overflow","presto":"text-overflow","trident":"-ms-text-overflow","status":"WD"}, -{"gecko":"","webkit":"-webkit-text-security","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-text-size-adjust","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-stroke","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-stroke-color","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-stroke-width","presto":"","trident":"","status":"P"}, -{"gecko":"text-transform","webkit":"-epub-text-transform","presto":"text-transform","trident":"text-transform","status":""}, -{"gecko":"","webkit":"-webkit-text-underline-position","presto":"","trident":"-ms-text-underline-position","status":"P"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-touch-action","status":""}, -{"gecko":"","webkit":"-webkit-touch-callout","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-transform","webkit":"-webkit-transform","presto":"-o-transform","trident":"transform","status":"WD"}, -{"gecko":"transform-origin","webkit":"-webkit-transform-origin","presto":"-o-transform-origin","trident":"transform-origin","status":"WD"}, -{"gecko":"","webkit":"-webkit-transform-origin-x","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-transform-origin-y","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-transform-origin-z","presto":"","trident":"","status":"P"}, -{"gecko":"transform-style","webkit":"-webkit-transform-style","presto":"","trident":"transform-style","status":"WD"}, -{"gecko":"transition","webkit":"-webkit-transition","presto":"-o-transition","trident":"transition","status":"WD"}, -{"gecko":"transition-delay","webkit":"-webkit-transition-delay","presto":"-o-transition-delay","trident":"transition-delay","status":"WD"}, -{"gecko":"transition-duration","webkit":"-webkit-transition-duration","presto":"-o-transition-duration","trident":"transition-duration","status":"WD"}, -{"gecko":"transition-property","webkit":"-webkit-transition-property","presto":"-o-transition-property","trident":"transition-property","status":"WD"}, -{"gecko":"transition-timing-function","webkit":"-webkit-transition-timing-function","presto":"-o-transition-timing-function","trident":"transition-timing-function","status":"WD"}, -{"gecko":"","webkit":"-webkit-user-drag","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-focus","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-input","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-modify","webkit":"-webkit-user-modify","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-select","webkit":"-webkit-user-select","presto":"","trident":"-ms-user-select","status":"P"}, -{"gecko":"","webkit":"","presto":"-xv-voice-balance","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-duration","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-pitch","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-pitch-range","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-rate","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-stress","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-volume","trident":"","status":""}, -{"gecko":"-moz-window-shadow","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"word-break","webkit":"-epub-word-break","presto":"","trident":"-ms-word-break","status":"WD"}, -{"gecko":"word-wrap","webkit":"word-wrap","presto":"word-wrap","trident":"-ms-word-wrap","status":"WD"}, -{"gecko":"","webkit":"-webkit-wrap-flow","presto":"","trident":"-ms-wrap-flow","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-wrap-margin","status":""}, -{"gecko":"","webkit":"-webkit-wrap-through","presto":"","trident":"-ms-wrap-through","status":""}, -{"gecko":"writing-mode","webkit":"-epub-writing-mode","presto":"writing-mode","trident":"-ms-writing-mode","status":"ED"}, -{"gecko":"","webkit":"zoom","presto":"","trident":"-ms-zoom","status":""}]}; - -var PrefixHelper = { - - mVENDOR_PREFIXES: null, - - kEXPORTS_FOR_GECKO: true, - kEXPORTS_FOR_WEBKIT: true, - kEXPORTS_FOR_PRESTO: true, - kEXPORTS_FOR_TRIDENT: true, - - cleanPrefixes: function() - { - this.mVENDOR_PREFIXES = null; - }, - - prefixesForProperty: function(aProperty) - { - if (!this.mVENDOR_PREFIXES) { - - this.mVENDOR_PREFIXES = {}; - for (var i = 0; i < kCSS_VENDOR_PREFIXES.properties.length; i++) { - var p = kCSS_VENDOR_PREFIXES.properties[i]; - if (p.gecko && (p.webkit || p.presto || p.trident)) { - var o = {}; - if (this.kEXPORTS_FOR_GECKO) o[p.gecko] = true; - if (this.kEXPORTS_FOR_WEBKIT && p.webkit) o[p.webkit] = true; - if (this.kEXPORTS_FOR_PRESTO && p.presto) o[p.presto] = true; - if (this.kEXPORTS_FOR_TRIDENT && p.trident) o[p.trident] = true; - this.mVENDOR_PREFIXES[p.gecko] = []; - for (var j in o) - this.mVENDOR_PREFIXES[p.gecko].push(j) - } - } - } - if (aProperty in this.mVENDOR_PREFIXES) - return this.mVENDOR_PREFIXES[aProperty].sort(); - return null; - } -}; - -function ParseURL(buffer) { - var result = { }; - result.protocol = ""; - result.user = ""; - result.password = ""; - result.host = ""; - result.port = ""; - result.path = ""; - result.query = ""; - - var section = "PROTOCOL"; - var start = 0; - var wasSlash = false; - - while(start < buffer.length) { - if(section == "PROTOCOL") { - if(buffer.charAt(start) == ':') { - section = "AFTER_PROTOCOL"; - start++; - } else if(buffer.charAt(start) == '/' && result.protocol.length() == 0) { - section = PATH; - } else { - result.protocol += buffer.charAt(start++); - } - } else if(section == "AFTER_PROTOCOL") { - if(buffer.charAt(start) == '/') { - if(!wasSlash) { - wasSlash = true; - } else { - wasSlash = false; - section = "USER"; - } - start ++; - } else { - throw new ParseException("Protocol shell be separated with 2 slashes"); - } - } else if(section == "USER") { - if(buffer.charAt(start) == '/') { - result.host = result.user; - result.user = ""; - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - result.host = result.user; - result.user = ""; - section = "QUERY"; - start++; - } else if(buffer.charAt(start) == ':') { - section = "PASSWORD"; - start++; - } else if(buffer.charAt(start) == '@') { - section = "HOST"; - start++; - } else { - result.user += buffer.charAt(start++); - } - } else if(section == "PASSWORD") { - if(buffer.charAt(start) == '/') { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - section = "QUERY"; - start ++; - } else if(buffer.charAt(start) == '@') { - section = "HOST"; - start++; - } else { - result.password += buffer.charAt(start++); - } - } else if(section == "HOST") { - if(buffer.charAt(start) == '/') { - section = "PATH"; - } else if(buffer.charAt(start) == ':') { - section = "PORT"; - start++; - } else if(buffer.charAt(start) == '?') { - section = "QUERY"; - start++; - } else { - result.host += buffer.charAt(start++); - } - } else if(section == "PORT") { - if(buffer.charAt(start) == '/') { - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - section = "QUERY"; - start++; - } else { - result.port += buffer.charAt(start++); - } - } else if(section == "PATH") { - if(buffer.charAt(start) == '?') { - section = "QUERY"; - start ++; - } else { - result.path += buffer.charAt(start++); - } - } else if(section == "QUERY") { - result.query += buffer.charAt(start++); - } - } - - if(section == "PROTOCOL") { - result.host = result.protocol; - result.protocol = "http"; - } else if(section == "AFTER_PROTOCOL") { - throw new ParseException("Invalid url"); - } else if(section == "USER") { - result.host = result.user; - result.user = ""; - } else if(section == "PASSWORD") { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - } - - return result; -} - -function ParseException(description) { - this.description = description; -} - -function CountLF(s) -{ - var nCR = s.match( /\n/g ); - return nCR ? nCR.length + 1 : 1; -} - -function DisposablePartialParsing(aStringToParse, aMethodName) -{ - var parser = new CSSParser(); - parser._init(); - parser.mPreserveWS = false; - parser.mPreserveComments = false; - parser.mPreservedTokens = []; - parser.mScanner.init(aStringToParse); - - return parser[aMethodName](); -} - -function FilterLinearGradient(aValue, aEngine) -{ - var d = DisposablePartialParsing(aValue, "parseBackgroundImages"); - if (!d) - return null; - var g = d[0]; - if (!g.value) - return null; - - var str = ""; - var position = ("position" in g.value) ? g.value.position.toLowerCase() : ""; - var angle = ("angle" in g.value) ? g.value.angle.toLowerCase() : ""; - - if ("webkit20110101" == aEngine) { - var cancelled = false; - str = "-webkit-gradient(linear, "; - // normalize angle - if (angle) { - var match = angle.match(/^([0-9\-\.\\+]+)([a-z]*)/); - var angle = parseFloat(match[1]); - var unit = match[2]; - switch (unit) { - case "grad": angle = angle * 90 / 100; break; - case "rad": angle = angle * 180 / Math.PI; break; - default: break; - } - while (angle < 0) - angle += 360; - while (angle >= 360) - angle -= 360; - } - // get startpoint w/o keywords - var startpoint = []; - var endpoint = []; - if (position != "") { - if (position == "center") - position = "center center"; - startpoint = position.split(" "); - if (angle == "" && angle != 0) { - // no angle, then we just turn the point 180 degrees around center - switch (startpoint[0]) { - case "left": endpoint.push("right"); break; - case "center": endpoint.push("center"); break; - case "right": endpoint.push("left"); break; - default: { - var match = startpoint[0].match(/^([0-9\-\.\\+]+)([a-z]*)/); - var v = parseFloat(match[0]); - var unit = match[1]; - if (unit == "%") { - endpoint.push((100-v) + "%"); - } - else - cancelled = true; - } - break; - } - if (!cancelled) - switch (startpoint[1]) { - case "top": endpoint.push("bottom"); break; - case "center": endpoint.push("center"); break; - case "bottom": endpoint.push("top"); break; - default: { - var match = startpoint[1].match(/^([0-9\-\.\\+]+)([a-z]*)/); - var v = parseFloat(match[0]); - var unit = match[1]; - if (unit == "%") { - endpoint.push((100-v) + "%"); - } - else - cancelled = true; - } - break; - } - } - else { - switch (angle) { - case 0: endpoint.push("right"); endpoint.push(startpoint[1]); break; - case 90: endpoint.push(startpoint[0]); endpoint.push("top"); break; - case 180: endpoint.push("left"); endpoint.push(startpoint[1]); break; - case 270: endpoint.push(startpoint[0]); endpoint.push("bottom"); break; - default: cancelled = true; break; - } - } - } - else { - // no position defined, we accept only vertical and horizontal - if (angle == "") - angle = 270; - switch (angle) { - case 0: startpoint= ["left", "center"]; endpoint = ["right", "center"]; break; - case 90: startpoint= ["center", "bottom"]; endpoint = ["center", "top"]; break; - case 180: startpoint= ["right", "center"]; endpoint = ["left", "center"]; break; - case 270: startpoint= ["center", "top"]; endpoint = ["center", "bottom"]; break; - default: cancelled = true; break; - } - } - - if (cancelled) - return ""; - - str += startpoint.join(" ") + ", " + endpoint.join(" "); - if (!g.value.stops[0].position) - g.value.stops[0].position = "0%"; - if (!g.value.stops[g.value.stops.length-1].position) - g.value.stops[g.value.stops.length-1].position = "100%"; - var current = 0; - for (var i = 0; i < g.value.stops.length && !cancelled; i++) { - var s = g.value.stops[i]; - if (s.position) { - if (s.position.indexOf("%") == -1) { - cancelled = true; - break; - } - } - else { - var j = i + 1; - while (j < g.value.stops.length && !g.value.stops[j].position) - j++; - var inc = parseFloat(g.value.stops[j].position) - current; - for (var k = i; k < j; k++) { - g.value.stops[k].position = (current + inc * (k - i + 1) / (j - i + 1)) + "%"; - } - } - current = parseFloat(s.position); - str += ", color-stop(" + (parseFloat(current) / 100) + ", " + s.color + ")"; - } - - if (cancelled) - return ""; - } - else { - str = (g.value.isRepeating ? "repeating-" : "") + "linear-gradient("; - if (angle || position) - str += (angle ? angle : position) + ", "; - - for (var i = 0; i < g.value.stops.length; i++) { - var s = g.value.stops[i]; - str += s.color - + (s.position ? " " + s.position : "") - + ((i != g.value.stops.length -1) ? ", " : ""); - } - } - str += ")"; - - switch (aEngine) { - case "webkit": str = "-webkit-" + str; break; - case "gecko1.9.2": str = "-moz-" + str; break; - case "presto": str = "-o-" + str; break; - case "trident": str = "-ms-" + str; break; - default: break; - } - return str; -} - -function FilterRadialGradient(aValue, aEngine) -{ - var d = DisposablePartialParsing(aValue, "parseBackgroundImages"); - if (!d) - return null; - var g = d[0]; - if (!g.value) - return null; - - // oh come on, this is now so painful to deal with ; no way I'm going to implement this - if ("webkit20110101" == aEngine) - return null; - - var str = (g.value.isRepeating ? "repeating-" : "") + "radial-gradient("; - var shape = ("shape" in g.value) ? g.value.shape : ""; - var extent = ("extent" in g.value) ? g.value.extent : ""; - var lengths = ""; - switch (g.value.positions.length) { - case 1: - lengths = g.value.positions[0] + " " + g.value.positions[0]; - break; - case 2: - lengths = g.value.positions[0] + " " + g.value.positions[1]; - break; - default: - break; - } - var at = g.value.at; - - str += (at ? at + ", " : "") - + ((shape || extent || at) - ? (shape ? shape + " " : "") - + (extent ? extent + " " : "") - + (lengths ? lengths + " " : "") - + ", " - : ""); - for (var i = 0; i < g.value.stops.length; i++) { - var s = g.value.stops[i]; - str += s.color - + (s.position ? " " + s.position : "") - + ((i != g.value.stops.length -1) ? ", " : ""); - } - str += ")"; - - switch (aEngine) { - case "webkit": str = "-webkit-" + str; break; - case "gecko1.9.2": str = "-moz-" + str; break; - case "presto": str = "-o-" + str; break; - case "trident": str = "-ms-" + str; break; - default: break; - } - return str; -} - -var CSS_ESCAPE = '\\'; - -var IS_HEX_DIGIT = 1; -var START_IDENT = 2; -var IS_IDENT = 4; -var IS_WHITESPACE = 8; - -var W = IS_WHITESPACE; -var I = IS_IDENT; -var S = START_IDENT; -var SI = IS_IDENT|START_IDENT; -var XI = IS_IDENT |IS_HEX_DIGIT; -var XSI = IS_IDENT|START_IDENT|IS_HEX_DIGIT; - -function CSSScanner(aString) -{ - this.init(aString); -} - -CSSScanner.prototype = { - - kLexTable: [ - // TAB LF FF CR - 0, 0, 0, 0, 0, 0, 0, 0, 0, W, W, 0, W, W, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // SPC ! " # $ % & ' ( ) * + , - . / - W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, I, 0, 0, - // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? - XI, XI, XI, XI, XI, XI, XI, XI, XI, XI, 0, 0, 0, 0, 0, 0, - // @ A B C D E F G H I J K L M N O - 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI, - // P Q R S T U V W X Y Z [ \ ] ^ _ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, S, 0, 0, SI, - // ` a b c d e f g h i j k l m n o - 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI, - // p q r s t u v w x y z { | } ~ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, 0, 0, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ - 0, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // à á â ã ä å æ ç è é ê ë ì í î ï - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI - ], - - kHexValues: { - "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, - "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15 - }, - - mString : "", - mPos : 0, - mPreservedPos : [], - - init: function(aString) { - this.mString = aString; - this.mPos = 0; - this.mPreservedPos = []; - }, - - getCurrentPos: function() { - return this.mPos; - }, - - getAlreadyScanned: function() - { - return this.mString.substr(0, this.mPos); - }, - - preserveState: function() { - this.mPreservedPos.push(this.mPos); - }, - - restoreState: function() { - if (this.mPreservedPos.length) { - this.mPos = this.mPreservedPos.pop(); - } - }, - - forgetState: function() { - if (this.mPreservedPos.length) { - this.mPreservedPos.pop(); - } - }, - - read: function() { - if (this.mPos < this.mString.length) - return this.mString.charAt(this.mPos++); - return -1; - }, - - peek: function() { - if (this.mPos < this.mString.length) - return this.mString.charAt(this.mPos); - return -1; - }, - - isHexDigit: function(c) { - var code = c.charCodeAt(0); - return (code < 256 && (this.kLexTable[code] & IS_HEX_DIGIT) != 0); - }, - - isIdentStart: function(c) { - var code = c.charCodeAt(0); - return (code >= 256 || (this.kLexTable[code] & START_IDENT) != 0); - }, - - startsWithIdent: function(aFirstChar, aSecondChar) { - var code = aFirstChar.charCodeAt(0); - return this.isIdentStart(aFirstChar) || - (aFirstChar == "-" && this.isIdentStart(aSecondChar)); - }, - - isIdent: function(c) { - var code = c.charCodeAt(0); - return (code >= 256 || (this.kLexTable[code] & IS_IDENT) != 0); - }, - - pushback: function() { - this.mPos--; - }, - - nextHexValue: function() { - var c = this.read(); - if (c == -1 || !this.isHexDigit(c)) - return new jscsspToken(jscsspToken.NULL_TYPE, null); - var s = c; - c = this.read(); - while (c != -1 && this.isHexDigit(c)) { - s += c; - c = this.read(); - } - if (c != -1) - this.pushback(); - return new jscsspToken(jscsspToken.HEX_TYPE, s); - }, - - gatherEscape: function() { - var c = this.peek(); - if (c == -1) - return ""; - if (this.isHexDigit(c)) { - var code = 0; - for (var i = 0; i < 6; i++) { - c = this.read(); - if (this.isHexDigit(c)) - code = code * 16 + this.kHexValues[c.toLowerCase()]; - else if (!this.isHexDigit(c) && !this.isWhiteSpace(c)) { - this.pushback(); - break; - } - else - break; - } - if (i == 6) { - c = this.peek(); - if (this.isWhiteSpace(c)) - this.read(); - } - return String.fromCharCode(code); - } - c = this.read(); - if (c != "\n") - return c; - return ""; - }, - - gatherIdent: function(c) { - var s = ""; - if (c == CSS_ESCAPE) - s += this.gatherEscape(); - else - s += c; - c = this.read(); - while (c != -1 - && (this.isIdent(c) || c == CSS_ESCAPE)) { - if (c == CSS_ESCAPE) - s += this.gatherEscape(); - else - s += c; - c = this.read(); - } - if (c != -1) - this.pushback(); - return s; - }, - - parseIdent: function(c) { - var value = this.gatherIdent(c); - var nextChar = this.peek(); - if (nextChar == "(") { - value += this.read(); - return new jscsspToken(jscsspToken.FUNCTION_TYPE, value); - } - return new jscsspToken(jscsspToken.IDENT_TYPE, value); - }, - - isDigit: function(c) { - return (c >= '0') && (c <= '9'); - }, - - parseComment: function(c) { - var s = c; - while ((c = this.read()) != -1) { - s += c; - if (c == "*") { - c = this.read(); - if (c == -1) - break; - if (c == "/") { - s += c; - break; - } - this.pushback(); - } - } - return new jscsspToken(jscsspToken.COMMENT_TYPE, s); - }, - - parseNumber: function(c) { - var s = c; - var foundDot = false; - while ((c = this.read()) != -1) { - if (c == ".") { - if (foundDot) - break; - else { - s += c; - foundDot = true; - } - } else if (this.isDigit(c)) - s += c; - else - break; - } - - if (c != -1 && this.startsWithIdent(c, this.peek())) { // DIMENSION - var unit = this.gatherIdent(c); - s += unit; - return new jscsspToken(jscsspToken.DIMENSION_TYPE, s, unit); - } - else if (c == "%") { - s += "%"; - return new jscsspToken(jscsspToken.PERCENTAGE_TYPE, s); - } - else if (c != -1) - this.pushback(); - return new jscsspToken(jscsspToken.NUMBER_TYPE, s); - }, - - parseString: function(aStop) { - var s = aStop; - var previousChar = aStop; - var c; - while ((c = this.read()) != -1) { - if (c == aStop && previousChar != CSS_ESCAPE) { - s += c; - break; - } - else if (c == CSS_ESCAPE) { - c = this.peek(); - if (c == -1) - break; - else if (c == "\n" || c == "\r" || c == "\f") { - d = c; - c = this.read(); - // special for Opera that preserves \r\n... - if (d == "\r") { - c = this.peek(); - if (c == "\n") - c = this.read(); - } - } - else { - s += this.gatherEscape(); - c = this.peek(); - } - } - else if (c == "\n" || c == "\r" || c == "\f") { - break; - } - else - s += c; - - previousChar = c; - } - return new jscsspToken(jscsspToken.STRING_TYPE, s); - }, - - isWhiteSpace: function(c) { - var code = c.charCodeAt(0); - return code < 256 && (this.kLexTable[code] & IS_WHITESPACE) != 0; - }, - - eatWhiteSpace: function(c) { - var s = c; - while ((c = this.read()) != -1) { - if (!this.isWhiteSpace(c)) - break; - s += c; - } - if (c != -1) - this.pushback(); - return s; - }, - - parseAtKeyword: function(c) { - return new jscsspToken(jscsspToken.ATRULE_TYPE, this.gatherIdent(c)); - }, - - nextToken: function() { - var c = this.read(); - if (c == -1) - return new jscsspToken(jscsspToken.NULL_TYPE, null); - - if (this.startsWithIdent(c, this.peek())) - return this.parseIdent(c); - - if (c == '@') { - var nextChar = this.read(); - if (nextChar != -1) { - var followingChar = this.peek(); - this.pushback(); - if (this.startsWithIdent(nextChar, followingChar)) - return this.parseAtKeyword(c); - } - } - - if (c == "." || c == "+" || c == "-") { - var nextChar = this.peek(); - if (this.isDigit(nextChar)) - return this.parseNumber(c); - else if (nextChar == "." && c != ".") { - firstChar = this.read(); - var secondChar = this.peek(); - this.pushback(); - if (this.isDigit(secondChar)) - return this.parseNumber(c); - } - } - if (this.isDigit(c)) { - return this.parseNumber(c); - } - - if (c == "'" || c == '"') - return this.parseString(c); - - if (this.isWhiteSpace(c)) { - var s = this.eatWhiteSpace(c); - - return new jscsspToken(jscsspToken.WHITESPACE_TYPE, s); - } - - if (c == "|" || c == "~" || c == "^" || c == "$" || c == "*") { - var nextChar = this.read(); - if (nextChar == "=") { - switch (c) { - case "~" : - return new jscsspToken(jscsspToken.INCLUDES_TYPE, "~="); - case "|" : - return new jscsspToken(jscsspToken.DASHMATCH_TYPE, "|="); - case "^" : - return new jscsspToken(jscsspToken.BEGINSMATCH_TYPE, "^="); - case "$" : - return new jscsspToken(jscsspToken.ENDSMATCH_TYPE, "$="); - case "*" : - return new jscsspToken(jscsspToken.CONTAINSMATCH_TYPE, "*="); - default : - break; - } - } else if (nextChar != -1) - this.pushback(); - } - - if (c == "/" && this.peek() == "*") - return this.parseComment(c); - - return new jscsspToken(jscsspToken.SYMBOL_TYPE, c); - } -}; - -CSSParser.prototype.parseBackgroundImages = function() -{ - var backgrounds = []; - var token = this.getToken(true, true); - while (token.isNotNull()) { - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - backgrounds.push( { type: "image", value: "url(" + urlContent }); - token = this.getToken(true, true); - } - else if (token.isFunction("linear-gradient(") - || token.isFunction("radial-gradient(") - || token.isFunction("repeating-linear-gradient(") - || token.isFunction("repeating-radial-gradient(")) { - this.ungetToken(); - var gradient = this.parseGradient(); - if (gradient) { - backgrounds.push({ - type: gradient.isRadial ? "radial-gradient" : "linear-gradient", - value: gradient - }); - token = this.getToken(true, true); - } - else - return null; - } - else if (token.isIdent("none") - || token.isIdent("inherit") - || token.isIdent("initial")) { - backgrounds.push( { type: token.value }); - token = this.getToken(true, true); - } - else - return null; - - if (token.isSymbol(",")) { - token = this.getToken(true, true); - if (!token.isNotNull()) - return null; - } - } - return backgrounds; -}; - -CSSParser.prototype.parseBackgroundShorthand = function(token, aDecl, aAcceptPriority) -{ - var kHPos = { - "left" : true, - "right" : true - }; - var kVPos = { - "top" : true, - "bottom" : true - }; - var kPos = { - "left" : true, - "right" : true, - "top" : true, - "bottom" : true, - "center" : true - }; - - var bgColor = null; - var bgRepeat = null; - var bgAttachment = null; - var bgImage = null; - var bgPosition = null; - - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!bgColor - && !bgRepeat - && !bgAttachment - && !bgImage - && !bgPosition - && token.isIdent(this.kINHERIT)) { - bgColor = this.kINHERIT; - bgRepeat = this.kINHERIT; - bgAttachment = this.kINHERIT; - bgImage = this.kINHERIT; - bgPosition = this.kINHERIT; - } - - else { - if (!bgAttachment - && (token.isIdent("scroll") || token.isIdent("fixed"))) { - bgAttachment = token.value; - } - - else if (!bgPosition - && ((token.isIdent() && token.value in kPos) - || token.isDimension() - || token.isNumber("0") - || token.isPercentage())) { - bgPosition = token.value; - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || token.isPercentage()) { - bgPosition += " " + token.value; - } else if (token.isIdent() && token.value in kPos) { - if ((bgPosition in kHPos && token.value in kHPos) - || (bgPosition in kVPos && token.value in kVPos)) - return ""; - bgPosition += " " + token.value; - } else { - this.ungetToken(); - bgPosition += " center"; - } - } - - else if (!bgRepeat - && (token.isIdent("repeat") - || token.isIdent("repeat-x") - || token.isIdent("repeat-y") - || token.isIdent("no-repeat"))) { - bgRepeat = token.value; - } - - else if (!bgImage - && (token.isFunction("url(") || token.isIdent("none"))) { - bgImage = token.value; - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var url = this.parseURL(token); // TODO - if (url) - bgImage += url; - else - return ""; - } - } - - else if (!bgImage - && (token.isFunction("linear-gradient(") - || token.isFunction("radial-gradient(") - || token.isFunction("repeating-linear-gradient(") || token.isFunction("repeating-radial-gradient("))) { - this.ungetToken(); - var gradient = this.parseGradient(); - if (gradient) - bgImage = this.serializeGradient(gradient); - else - return ""; - } - - else { - var color = this.parseColor(token); - if (!bgColor && color) - bgColor = color; - else - return ""; - } - - } - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - bgColor = bgColor ? bgColor : "transparent"; - bgImage = bgImage ? bgImage : "none"; - bgRepeat = bgRepeat ? bgRepeat : "repeat"; - bgAttachment = bgAttachment ? bgAttachment : "scroll"; - bgPosition = bgPosition ? bgPosition : "top left"; - - aDecl.push(this._createJscsspDeclarationFromValue("background-color", bgColor)); - aDecl.push(this._createJscsspDeclarationFromValue("background-image", bgImage)); - aDecl.push(this._createJscsspDeclarationFromValue("background-repeat", bgRepeat)); - aDecl.push(this._createJscsspDeclarationFromValue("background-attachment", bgAttachment)); - aDecl.push(this._createJscsspDeclarationFromValue("background-position", bgPosition)); - - return bgColor + " " + bgImage + " " + bgRepeat + " " + bgAttachment + " " + bgPosition; -}; -CSSParser.prototype.parseBorderColorShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - token = this.getToken(true, true); - break; - } - - else { - var color = this.parseColor(token); - if (color) - values.push(color); - else - return ""; - } - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-color", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-color", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-color", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-color", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBorderEdgeOrOutlineShorthand = function(token, aDecl, aAcceptPriority, aProperty) -{ - var bWidth = null; - var bStyle = null; - var bColor = null; - - while (true) { - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!bWidth - && !bStyle - && !bColor - && token.isIdent(this.kINHERIT)) { - bWidth = this.kINHERIT; - bStyle = this.kINHERIT; - bColor = this.kINHERIT; - } - - else if (!bWidth && - (token.isDimension() - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES) - || token.isNumber("0"))) { - bWidth = token.value; - } - - else if (!bStyle && - (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES)) { - bStyle = token.value; - } - - else { - var color = (aProperty == "outline" && token.isIdent("invert")) - ? "invert" : this.parseColor(token); - if (!bColor && color) - bColor = color; - else - return ""; - } - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - bWidth = bWidth ? bWidth : "medium"; - bStyle = bStyle ? bStyle : "none"; - bColor = bColor ? bColor : "-moz-initial"; - - function addPropertyToDecl(aSelf, aDecl, property, w, s, c) { - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-width", w)); - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-style", s)); - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-color", c)); - } - - if (aProperty == "border") { - addPropertyToDecl(this, aDecl, "border-top", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-right", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-bottom", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-left", bWidth, bStyle, bColor); - } - else - addPropertyToDecl(this, aDecl, aProperty, bWidth, bStyle, bColor); - return bWidth + " " + bStyle + " " + bColor; -}; - -CSSParser.prototype.parseBorderImage = function() -{ - var borderImage = {url: "", offsets: [], widths: [], sizes: []}; - var token = this.getToken(true, true); - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - borderImage.url = urlContent.substr(0, urlContent.length - 1).trim(); - if ((borderImage.url[0] == '"' && borderImage.url[borderImage.url.length - 1] == '"') - || (borderImage.url[0] == "'" && borderImage.url[borderImage.url.length - 1] == "'")) - borderImage.url = borderImage.url.substr(1, borderImage.url.length - 2); - } - else - return null; - } - else - return null; - - token = this.getToken(true, true); - if (token.isNumber() - || token.isPercentage()) - borderImage.offsets.push(token.value); - else - return null; - var i; - for (i= 0; i < 3; i++) { - token = this.getToken(true, true); - if (token.isNumber() - || token.isPercentage()) - borderImage.offsets.push(token.value); - else - break; - } - if (i == 3) - token = this.getToken(true, true); - - if (token.isSymbol("/")) { - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) - borderImage.widths.push(token.value); - else - return null; - - for (var i = 0; i < 3; i++) { - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) - borderImage.widths.push(token.value); - else - break; - } - if (i == 3) - token = this.getToken(true, true); - } - - for (var i = 0; i < 2; i++) { - if (token.isIdent("stretch") - || token.isIdent("repeat") - || token.isIdent("round")) - borderImage.sizes.push(token.value); - else if (!token.isNotNull()) - return borderImage; - else - return null; - token = this.getToken(true, true); - } - if (!token.isNotNull()) - return borderImage; - - return null; -}; - -CSSParser.prototype.parseBorderStyleShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-style", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-style", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-style", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-style", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBorderWidthShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-width", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-width", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-width", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-width", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBoxShadows = function() -{ - var shadows = []; - var token = this.getToken(true, true); - var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px", spreadRadius = "0px"; - var inset = false; - while (token.isNotNull()) { - if (token.isIdent("none")) { - shadows.push( { none: true } ); - token = this.getToken(true, true); - } - else { - if (token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetX = token.value; - token = this.getToken(true, true); - } - else - return []; - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetY = token.value; - token = this.getToken(true, true); - } - else - return []; - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var blurRadius = token.value; - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var spreadRadius = token.value; - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent()) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - shadows.push( { none: false, - color: color, - offsetX: offsetX, offsetY: offsetY, - blurRadius: blurRadius, - spreadRadius: spreadRadius, - inset: inset - } ); - - if (token.isSymbol(",")) { - inset = false; - color = ""; - blurRadius = "0px"; - spreadRadius = "0px" - offsetX = "0px"; - offsetY = "0px"; - token = this.getToken(true, true); - } - else if (!token.isNotNull()) - return shadows; - else - return []; - } - } - return shadows; -}; - -CSSParser.prototype.parseCharsetRule = function(aSheet) { - var token = this.getToken(false, false); - if (token.isAtRule("@charset") && token.value == "@charset") { // lowercase check - var s = token.value; - token = this.getToken(false, false); - s += token.value; - if (token.isWhiteSpace(" ")) { - token = this.getToken(false, false); - s += token.value; - if (token.isString()) { - var encoding = token.value; - token = this.getToken(false, false); - s += token.value; - if (token.isSymbol(";")) { - var rule = new jscsspCharsetRule(); - rule.encoding = encoding; - rule.parsedCssText = s; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - else - this.reportError(kCHARSET_RULE_MISSING_SEMICOLON); - } - else - this.reportError(kCHARSET_RULE_CHARSET_IS_STRING); - } - else - this.reportError(kCHARSET_RULE_MISSING_WS); - } - - this.addUnknownAtRule(aSheet, s); - return false; -}; - -CSSParser.prototype.parseColor = function(token) -{ - var color = ""; - if (token.isFunction("rgb(") - || token.isFunction("rgba(")) { - color = token.value; - var isRgba = token.isFunction("rgba(") - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - - if (isRgba) { - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - } - - token = this.getToken(true, true); - if (!token.isSymbol(")")) - return ""; - color += token.value; - } - - else if (token.isFunction("hsl(") - || token.isFunction("hsla(")) { - color = token.value; - var isHsla = token.isFunction("hsla(") - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isPercentage()) - return ""; - color += token.value; - - if (isHsla) { - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - } - - token = this.getToken(true, true); - if (!token.isSymbol(")")) - return ""; - color += token.value; - } - - else if (token.isIdent() - && (token.value in this.kCOLOR_NAMES)) - color = token.value; - - else if (token.isSymbol("#")) { - token = this.getHexValue(); - if (!token.isHex()) - return ""; - var length = token.value.length; - if (length != 3 && length != 6) - return ""; - if (token.value.match( /[a-fA-F0-9]/g ).length != length) - return ""; - color = "#" + token.value; - } - return color; -}; - -CSSParser.prototype.parseCueShorthand = function(token, declarations, aAcceptPriority) -{ - var before = ""; - var after = ""; - - var values = []; - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isIdent("none")) - values.push(token.value); - - else if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) - values.push("url(" + urlContent); - else - return ""; - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - before = values[0]; - after = before; - break; - case 2: - before = values[0]; - after = values[1]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("cue-before", before)); - aDecl.push(this._createJscsspDeclarationFromValue("cue-after", after)); - return before + " " + after; -}; - -CSSParser.prototype.parseDeclaration = function(aToken, aDecl, aAcceptPriority, aExpandShorthands, aSheet) { - this.preserveState(); - var blocks = []; - if (aToken.isIdent()) { - var descriptor = aToken.value.toLowerCase(); - var token = this.getToken(true, true); - if (token.isSymbol(":")) { - var token = this.getToken(true, true); - - var value = ""; - var declarations = []; - if (aExpandShorthands) - switch (descriptor) { - case "background": - value = this.parseBackgroundShorthand(token, declarations, aAcceptPriority); - break; - case "margin": - case "padding": - value = this.parseMarginOrPaddingShorthand(token, declarations, aAcceptPriority, descriptor); - break; - case "border-color": - value = this.parseBorderColorShorthand(token, declarations, aAcceptPriority); - break; - case "border-style": - value = this.parseBorderStyleShorthand(token, declarations, aAcceptPriority); - break; - case "border-width": - value = this.parseBorderWidthShorthand(token, declarations, aAcceptPriority); - break; - case "border-top": - case "border-right": - case "border-bottom": - case "border-left": - case "border": - case "outline": - value = this.parseBorderEdgeOrOutlineShorthand(token, declarations, aAcceptPriority, descriptor); - break; - case "cue": - value = this.parseCueShorthand(token, declarations, aAcceptPriority); - break; - case "pause": - value = this.parsePauseShorthand(token, declarations, aAcceptPriority); - break; - case "font": - value = this.parseFontShorthand(token, declarations, aAcceptPriority); - break; - case "list-style": - value = this.parseListStyleShorthand(token, declarations, aAcceptPriority); - break; - default: - value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet); - break; - } - else - value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet); - token = this.currentToken(); - if (value) // no error above - { - var priority = false; - if (token.isSymbol("!")) { - token = this.getToken(true, true); - if (token.isIdent("important")) { - priority = true; - token = this.getToken(true, true); - if (token.isSymbol(";") || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - } - else return ""; - } - else return ""; - } - else if (token.isNotNull() && !token.isSymbol(";") && !token.isSymbol("}")) - return ""; - for (var i = 0; i < declarations.length; i++) { - declarations[i].priority = priority; - aDecl.push(declarations[i]); - } - return descriptor + ": " + value + ";"; - } - } - } - else if (aToken.isComment()) { - if (this.mPreserveComments) { - this.forgetState(); - var comment = new jscsspComment(); - comment.parsedCssText = aToken.value; - aDecl.push(comment); - } - return aToken.value; - } - - // we have an error here, let's skip it - this.restoreState(); - var s = aToken.value; - blocks = []; - var token = this.getToken(false, false); - while (token.isNotNull()) { - s += token.value; - if ((token.isSymbol(";") || token.isSymbol("}")) && !blocks.length) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[") - || token.isFunction()) { - blocks.push(token.isFunction() ? "(" : token.value); - } else if (token.isSymbol("}") - || token.isSymbol(")") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - } - } - } - token = this.getToken(false, false); - } - return ""; -}; - -CSSParser.prototype.reportError = function(aMsg) { - this.mError = aMsg; -}; - -CSSParser.prototype.consumeError = function() { - var e = this.mError; - this.mError = null; - return e; -}; - -function CSSParser(aString) -{ - this.mToken = null; - this.mLookAhead = null; - this.mScanner = new CSSScanner(aString); - - this.mPreserveWS = true; - this.mPreserveComments = true; - - this.mPreservedTokens = []; - - this.mError = null; -} - -CSSParser.prototype._init = function() { - this.mToken = null; - this.mLookAhead = null; -}; - -CSSParser.prototype.kINHERIT = "inherit", - -CSSParser.prototype.kBORDER_WIDTH_NAMES = { - "thin": true, - "medium": true, - "thick": true -}; - -CSSParser.prototype.kBORDER_STYLE_NAMES = { - "none": true, - "hidden": true, - "dotted": true, - "dashed": true, - "solid": true, - "double": true, - "groove": true, - "ridge": true, - "inset": true, - "outset": true -}; - -CSSParser.prototype.kCOLOR_NAMES = { - "transparent": true, - - "black": true, - "silver": true, - "gray": true, - "white": true, - "maroon": true, - "red": true, - "purple": true, - "fuchsia": true, - "green": true, - "lime": true, - "olive": true, - "yellow": true, - "navy": true, - "blue": true, - "teal": true, - "aqua": true, - - "aliceblue": true, - "antiquewhite": true, - "aquamarine": true, - "azure": true, - "beige": true, - "bisque": true, - "blanchedalmond": true, - "blueviolet": true, - "brown": true, - "burlywood": true, - "cadetblue": true, - "chartreuse": true, - "chocolate": true, - "coral": true, - "cornflowerblue": true, - "cornsilk": true, - "crimson": true, - "cyan": true, - "darkblue": true, - "darkcyan": true, - "darkgoldenrod": true, - "darkgray": true, - "darkgreen": true, - "darkgrey": true, - "darkkhaki": true, - "darkmagenta": true, - "darkolivegreen": true, - "darkorange": true, - "darkorchid": true, - "darkred": true, - "darksalmon": true, - "darkseagreen": true, - "darkslateblue": true, - "darkslategray": true, - "darkslategrey": true, - "darkturquoise": true, - "darkviolet": true, - "deeppink": true, - "deepskyblue": true, - "dimgray": true, - "dimgrey": true, - "dodgerblue": true, - "firebrick": true, - "floralwhite": true, - "forestgreen": true, - "gainsboro": true, - "ghostwhite": true, - "gold": true, - "goldenrod": true, - "greenyellow": true, - "grey": true, - "honeydew": true, - "hotpink": true, - "indianred": true, - "indigo": true, - "ivory": true, - "khaki": true, - "lavender": true, - "lavenderblush": true, - "lawngreen": true, - "lemonchiffon": true, - "lightblue": true, - "lightcoral": true, - "lightcyan": true, - "lightgoldenrodyellow": true, - "lightgray": true, - "lightgreen": true, - "lightgrey": true, - "lightpink": true, - "lightsalmon": true, - "lightseagreen": true, - "lightskyblue": true, - "lightslategray": true, - "lightslategrey": true, - "lightsteelblue": true, - "lightyellow": true, - "limegreen": true, - "linen": true, - "magenta": true, - "mediumaquamarine": true, - "mediumblue": true, - "mediumorchid": true, - "mediumpurple": true, - "mediumseagreen": true, - "mediumslateblue": true, - "mediumspringgreen": true, - "mediumturquoise": true, - "mediumvioletred": true, - "midnightblue": true, - "mintcream": true, - "mistyrose": true, - "moccasin": true, - "navajowhite": true, - "oldlace": true, - "olivedrab": true, - "orange": true, - "orangered": true, - "orchid": true, - "palegoldenrod": true, - "palegreen": true, - "paleturquoise": true, - "palevioletred": true, - "papayawhip": true, - "peachpuff": true, - "peru": true, - "pink": true, - "plum": true, - "powderblue": true, - "rosybrown": true, - "royalblue": true, - "saddlebrown": true, - "salmon": true, - "sandybrown": true, - "seagreen": true, - "seashell": true, - "sienna": true, - "skyblue": true, - "slateblue": true, - "slategray": true, - "slategrey": true, - "snow": true, - "springgreen": true, - "steelblue": true, - "tan": true, - "thistle": true, - "tomato": true, - "turquoise": true, - "violet": true, - "wheat": true, - "whitesmoke": true, - "yellowgreen": true, - - "activeborder": true, - "activecaption": true, - "appworkspace": true, - "background": true, - "buttonface": true, - "buttonhighlight": true, - "buttonshadow": true, - "buttontext": true, - "captiontext": true, - "graytext": true, - "highlight": true, - "highlighttext": true, - "inactiveborder": true, - "inactivecaption": true, - "inactivecaptiontext": true, - "infobackground": true, - "infotext": true, - "menu": true, - "menutext": true, - "scrollbar": true, - "threeddarkshadow": true, - "threedface": true, - "threedhighlight": true, - "threedlightshadow": true, - "threedshadow": true, - "window": true, - "windowframe": true, - "windowtext": true -}; - -CSSParser.prototype.kLIST_STYLE_TYPE_NAMES = { - "decimal": true, - "decimal-leading-zero": true, - "lower-roman": true, - "upper-roman": true, - "georgian": true, - "armenian": true, - "lower-latin": true, - "lower-alpha": true, - "upper-latin": true, - "upper-alpha": true, - "lower-greek": true, - - "disc": true, - "circle": true, - "square": true, - "none": true, - - /* CSS 3 */ - "box": true, - "check": true, - "diamond": true, - "hyphen": true, - - "lower-armenian": true, - "cjk-ideographic": true, - "ethiopic-numeric": true, - "hebrew": true, - "japanese-formal": true, - "japanese-informal": true, - "simp-chinese-formal": true, - "simp-chinese-informal": true, - "syriac": true, - "tamil": true, - "trad-chinese-formal": true, - "trad-chinese-informal": true, - "upper-armenian": true, - "arabic-indic": true, - "binary": true, - "bengali": true, - "cambodian": true, - "khmer": true, - "devanagari": true, - "gujarati": true, - "gurmukhi": true, - "kannada": true, - "lower-hexadecimal": true, - "lao": true, - "malayalam": true, - "mongolian": true, - "myanmar": true, - "octal": true, - "oriya": true, - "persian": true, - "urdu": true, - "telugu": true, - "tibetan": true, - "upper-hexadecimal": true, - "afar": true, - "ethiopic-halehame-aa-et": true, - "ethiopic-halehame-am-et": true, - "amharic-abegede": true, - "ehiopic-abegede-am-et": true, - "cjk-earthly-branch": true, - "cjk-heavenly-stem": true, - "ethiopic": true, - "ethiopic-abegede": true, - "ethiopic-abegede-gez": true, - "hangul-consonant": true, - "hangul": true, - "hiragana-iroha": true, - "hiragana": true, - "katakana-iroha": true, - "katakana": true, - "lower-norwegian": true, - "oromo": true, - "ethiopic-halehame-om-et": true, - "sidama": true, - "ethiopic-halehame-sid-et": true, - "somali": true, - "ethiopic-halehame-so-et": true, - "tigre": true, - "ethiopic-halehame-tig": true, - "tigrinya-er-abegede": true, - "ethiopic-abegede-ti-er": true, - "tigrinya-et": true, - "ethiopic-halehame-ti-et": true, - "upper-greek": true, - "asterisks": true, - "footnotes": true, - "circled-decimal": true, - "circled-lower-latin": true, - "circled-upper-latin": true, - "dotted-decimal": true, - "double-circled-decimal": true, - "filled-circled-decimal": true, - "parenthesised-decimal": true, - "parenthesised-lower-latin": true -}; - -CSSParser.prototype.parseFontFaceRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var descriptors = []; - this.preserveState(); - var token = this.getToken(true, true); - if (token.isNotNull()) { - // expecting block start - if (token.isSymbol("{")) { - s += " " + token.value; - var token = this.getToken(true, false); - while (true) { - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, descriptors, false, false, aSheet); - s += ((d && descriptors.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspFontFaceRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.descriptors = descriptors; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseFontShorthand = function(token, aDecl, aAcceptPriority) -{ - var kStyle = {"italic": true, "oblique": true }; - var kVariant = {"small-caps": true }; - var kWeight = { "bold": true, "bolder": true, "lighter": true, - "100": true, "200": true, "300": true, "400": true, - "500": true, "600": true, "700": true, "800": true, - "900": true }; - var kSize = { "xx-small": true, "x-small": true, "small": true, "medium": true, - "large": true, "x-large": true, "xx-large": true, - "larger": true, "smaller": true }; - var kValues = { "caption": true, "icon": true, "menu": true, "message-box": true, "small-caption": true, "status-bar": true }; - var kFamily = { "serif": true, "sans-serif": true, "cursive": true, "fantasy": true, "monospace": true }; - - var fStyle = null; - var fVariant = null; - var fWeight = null; - var fSize = null; - var fLineHeight = null; - var fFamily = ""; - var fSystem = null; - var fFamilyValues = []; - - var normalCount = 0; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!fStyle && !fVariant && !fWeight - && !fSize && !fLineHeight && !fFamily - && !fSystem - && token.isIdent(this.kINHERIT)) { - fStyle = this.kINHERIT; - fVariant = this.kINHERIT; - fWeight = this.kINHERIT; - fSize = this.kINHERIT; - fLineHeight = this.kINHERIT; - fFamily = this.kINHERIT; - fSystem = this.kINHERIT; - } - - else { - if (!fSystem && (token.isIdent() && token.value in kValues)) { - fSystem = token.value; - break; - } - - else { - if (!fStyle - && token.isIdent() - && (token.value in kStyle)) { - fStyle = token.value; - } - - else if (!fVariant - && token.isIdent() - && (token.value in kVariant)) { - fVariant = token.value; - } - - else if (!fWeight - && (token.isIdent() || token.isNumber()) - && (token.value in kWeight)) { - fWeight = token.value; - } - - else if (!fSize - && ((token.isIdent() && (token.value in kSize)) - || token.isDimension() - || token.isPercentage())) { - fSize = token.value; - token = this.getToken(false, false); - if (token.isSymbol("/")) { - token = this.getToken(false, false); - if (!fLineHeight && - (token.isDimension() || token.isNumber() || token.isPercentage())) { - fLineHeight = token.value; - } - else - return ""; - } - else if (!token.isWhiteSpace()) - continue; - } - - else if (token.isIdent("normal")) { - normalCount++; - if (normalCount > 3) - return ""; - } - - else if (!fFamily && // *MUST* be last to be tested here - (token.isString() - || token.isIdent())) { - var lastWasComma = false; - while (true) { - if (!token.isNotNull()) - break; - else if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - this.ungetToken(); - break; - } - else if (token.isIdent() && token.value in kFamily) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = token.value; - fFamilyValues.push(value); - fFamily += token.value; - break; - } - else if (token.isString() || token.isIdent()) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = token.value; - fFamilyValues.push(value); - fFamily += token.value; - lastWasComma = false; - } - else if (!lastWasComma && token.isSymbol(",")) { - fFamily += ", "; - lastWasComma = true; - } - else - return ""; - token = this.getToken(true, true); - } - } - - else { - return ""; - } - } - - } - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - if (fSystem) { - aDecl.push(this._createJscsspDeclarationFromValue("font", fSystem)); - return fSystem; - } - fStyle = fStyle ? fStyle : "normal"; - fVariant = fVariant ? fVariant : "normal"; - fWeight = fWeight ? fWeight : "normal"; - fSize = fSize ? fSize : "medium"; - fLineHeight = fLineHeight ? fLineHeight : "normal"; - fFamily = fFamily ? fFamily : "-moz-initial"; - - aDecl.push(this._createJscsspDeclarationFromValue("font-style", fStyle)); - aDecl.push(this._createJscsspDeclarationFromValue("font-variant", fVariant)); - aDecl.push(this._createJscsspDeclarationFromValue("font-weight", fWeight)); - aDecl.push(this._createJscsspDeclarationFromValue("font-size", fSize)); - aDecl.push(this._createJscsspDeclarationFromValue("line-height", fLineHeight)); - aDecl.push(this._createJscsspDeclarationFromValuesArray("font-family", fFamilyValues, fFamily)); - return fStyle + " " + fVariant + " " + fWeight + " " + fSize + "/" + fLineHeight + " " + fFamily; -}; - -CSSParser.prototype.parseFunctionArgument = function(token) -{ - var value = ""; - if (token.isString()) - { - value += token.value; - token = this.getToken(true, true); - } - else { - var parenthesis = 1; - while (true) - { - if (!token.isNotNull()) - return ""; - if (token.isFunction() || token.isSymbol("(")) - parenthesis++; - if (token.isSymbol(")")) { - parenthesis--; - if (!parenthesis) - break; - } - value += token.value; - token = this.getToken(false, false); - } - } - - if (token.isSymbol(")")) - return value + ")"; - return ""; -}; - -CSSParser.prototype.parseColorStop = function(token) -{ - var color = this.parseColor(token); - var position = ""; - if (!color) - return null; - token = this.getToken(true, true); - if (token.isLength()) { - position = token.value; - token = this.getToken(true, true); - } - return { color: color, position: position } -}; - -CSSParser.prototype.parseGradient = function () -{ - var kHPos = {"left": true, "right": true }; - var kVPos = {"top": true, "bottom": true }; - var kPos = {"left": true, "right": true, "top": true, "bottom": true, "center": true}; - - var isRadial = false; - var gradient = { isRepeating: false }; - var token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isFunction("linear-gradient(") || - token.isFunction("radial-gradient(") || - token.isFunction("repeating-linear-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - if (token.isFunction("radial-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - gradient.isRadial = true; - } - if (token.isFunction("repeating-linear-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - gradient.isRepeating = true; - } - - - token = this.getToken(true, true); - var foundPosition = false; - var haveAngle = false; - - /********** LINEAR **********/ - if (token.isAngle()) { - gradient.angle = token.value; - haveAngle = true; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - - else if (token.isIdent("to")) { - foundPosition = true; - token = this.getToken(true, true); - if (token.isIdent("top") - || token.isIdent("bottom") - || token.isIdent("left") - || token.isIdent("right")) { - gradient.position = token.value; - token = this.getToken(true, true); - if (((gradient.position == "top" || gradient.position == "bottom") && (token.isIdent("left") || token.isIdent("right"))) - || ((gradient.position == "left" || gradient.position == "right") && (token.isIdent("top") || token.isIdent("bottom")))) { - gradient.position += " " + token.value; - token = this.getToken(true, true); - } - } - else - return null; - - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - - /********** RADIAL **********/ - else if (gradient.isRadial) { - gradient.shape = ""; - gradient.extent = ""; - gradient.positions = []; - gradient.at = ""; - - while (!token.isIdent("at") && !token.isSymbol(",")) { - if (!gradient.shape - && (token.isIdent("circle") || token.isIdent("ellipse"))) { - gradient.shape = token.value; - token = this.getToken(true, true); - } - else if (!gradient.extent - && (token.isIdent("closest-corner") - || token.isIdent("closes-side") - || token.isIdent("farthest-corner") - || token.isIdent("farthest-corner"))) { - gradient.extent = token.value; - token = this.getToken(true, true); - } - else if (gradient.positions.length < 2 && token.isLength()){ - gradient.positions.push(token.value); - token = this.getToken(true, true); - } - else - break; - } - - // verify if the shape is null of well defined - if ((gradient.positions.length == 1 && !gradient.extent && (gradient.shape == "circle" || !gradient.shape)) - || (gradient.positions.length == 2 && !gradient.extent && (gradient.shape == "ellipse" || !gradient.shape)) - || (!gradient.positions.length && gradient.extent) - || (!gradient.positions.length && !gradient.extent)) { - // shape ok - } - else { - return null; - } - - if (token.isIdent("at")) { - token = this.getToken(true, true); - if (((token.isIdent() && token.value in kPos) - || token.isDimension() - || token.isNumber("0") - || token.isPercentage())) { - gradient.at = token.value; - token = this.getToken(true, true); - if (token.isDimension() || token.isNumber("0") || token.isPercentage()) { - gradient.at += " " + token.value; - } - else if (token.isIdent() && token.value in kPos) { - if ((gradient.at in kHPos && token.value in kHPos) || - (gradient.at in kVPos && token.value in kVPos)) - return ""; - gradient.at += " " + token.value; - } - else { - this.ungetToken(); - gradient.at += " center"; - } - } - else - return null; - - token = this.getToken(true, true); - } - - if (gradient.shape || gradient.extent || gradient.positions.length || gradient.at) { - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - } - - // now color stops... - var stop1 = this.parseColorStop(token); - if (!stop1) - return null; - token = this.currentToken(); - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - var stop2 = this.parseColorStop(token); - if (!stop2) - return null; - token = this.currentToken(); - if (token.isSymbol(",")) { - token = this.getToken(true, true); - } - // ok we have at least two color stops - gradient.stops = [stop1, stop2]; - while (!token.isSymbol(")")) { - var colorstop = this.parseColorStop(token); - if (!colorstop) - return null; - token = this.currentToken(); - if (!token.isSymbol(")") && !token.isSymbol(",")) - return null; - if (token.isSymbol(",")) - token = this.getToken(true, true); - gradient.stops.push(colorstop); - } - return gradient; - } - } - return null; -}; - -CSSParser.prototype.serializeGradient = function(gradient) -{ - var s = gradient.isRadial - ? (gradient.isRepeating ? "repeating-radial-gradient(" : "radial-gradient(" ) - : (gradient.isRepeating ? "repeating-linear-gradient(" : "linear-gradient(" ); - if (gradient.angle || gradient.position) - s += (gradient.angle ? gradient.angle: "") + - (gradient.position ? "to " + gradient.position : "") + - ", "; - - if (gradient.isRadial) - s += (gradient.shape ? gradient.shape + " " : "") + - (gradient.extent ? gradient.extent + " " : "") + - (gradient.positions.length ? gradient.positions.join(" ") + " " : "") + - (gradient.at ? "at " + gradient.at + " " : "") + - (gradient.shape || gradient.extent || gradient.positions.length || gradient.at ? ", " : ""); - - for (var i = 0; i < gradient.stops.length; i++) { - var colorstop = gradient.stops[i]; - s += colorstop.color + (colorstop.position ? " " + colorstop.position : ""); - if (i != gradient.stops.length -1) - s += ", "; - } - s += ")"; - return s; -}; - - -CSSParser.prototype.parseImportRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - this.preserveState(); - var token = this.getToken(true, true); - var media = []; - var href = ""; - if (token.isString()) { - href = token.value; - s += " " + href; - } - else if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - href = "url(" + urlContent; - s += " " + href; - } - } - else - this.reportError(kIMPORT_RULE_MISSING_URL); - - if (href) { - token = this.getToken(true, true); - while (token.isIdent()) { - s += " " + token.value; - media.push(token.value); - token = this.getToken(true, true); - if (!token) - break; - if (token.isSymbol(",")) { - s += ","; - } else if (token.isSymbol(";")) { - break; - } else - break; - token = this.getToken(true, true); - } - - if (!media.length) { - media.push("all"); - } - - if (token.isSymbol(";")) { - s += ";" - this.forgetState(); - var rule = new jscsspImportRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.href = href; - rule.media = media; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - } - - this.restoreState(); - this.addUnknownAtRule(aSheet, "@import"); - return false; -}; - -CSSParser.prototype.parseKeyframesRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var keyframesRule = new jscsspKeyframesRule(); - keyframesRule.currentLine = currentLine; - this.preserveState(); - var token = this.getToken(true, true); - var foundName = false; - while (token.isNotNull()) { - if (token.isIdent()) { - // should be the keyframes' name - foundName = true; - s += " " + token.value; - keyframesRule.name = token.value; - token = this.getToken(true, true); - if (token.isSymbol("{")) - this.ungetToken(); - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - else if (token.isSymbol("{")) { - if (!foundName) { - token.type = jscsspToken.NULL_TYPE; - // not a valid keyframes at-rule - } - break; - } - else { - token.type = jscsspToken.NULL_TYPE; - // not a valid keyframes at-rule - break; - } - token = this.getToken(true, true); - } - - if (token.isSymbol("{") && keyframesRule.name) { - // ok let's parse keyframe rules now... - s += " { "; - token = this.getToken(true, false); - while (token.isNotNull()) { - if (token.isComment() && this.mPreserveComments) { - s += " " + token.value; - var comment = new jscsspComment(); - comment.parsedCssText = token.value; - keyframesRule.cssRules.push(comment); - } else if (token.isSymbol("}")) { - valid = true; - break; - } else { - var r = this.parseKeyframeRule(token, keyframesRule, true); - if (r) - s += r; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - keyframesRule.currentLine = currentLine; - keyframesRule.parsedCssText = s; - aSheet.cssRules.push(keyframesRule); - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseKeyframeRule = function(aToken, aOwner) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - this.preserveState(); - var token = aToken; - - // find the keyframe keys - var key = ""; - while (token.isNotNull()) { - if (token.isIdent() || token.isPercentage()) { - if (token.isIdent() - && !token.isIdent("from") - && !token.isIdent("to")) { - key = ""; - break; - } - key += token.value; - token = this.getToken(true, true); - if (token.isSymbol("{")) { - this.ungetToken(); - break; - } - else - if (token.isSymbol(",")) { - key += ", "; - } - else { - key = ""; - break; - } - } - else { - key = ""; - break; - } - token = this.getToken(true, true); - } - - var valid = false; - var declarations = []; - if (key) { - var s = key; - token = this.getToken(true, true); - if (token.isSymbol("{")) { - s += " { "; - token = this.getToken(true, false); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aOwner); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - else { - // key is invalid so the whole rule is invalid with it - } - - if (valid) { - var rule = new jscsspKeyframeRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.keyText = key; - rule.parentRule = aOwner; - aOwner.cssRules.push(rule); - return s; - } - this.restoreState(); - s = this.currentToken().value; - this.addUnknownAtRule(aOwner, s); - return ""; -}; - -CSSParser.prototype.parseListStyleShorthand = function(token, aDecl, aAcceptPriority) -{ - var kPosition = { "inside": true, "outside": true }; - - var lType = null; - var lPosition = null; - var lImage = null; - - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!lType && !lPosition && ! lImage - && token.isIdent(this.kINHERIT)) { - lType = this.kINHERIT; - lPosition = this.kINHERIT; - lImage = this.kINHERIT; - } - - else if (!lType && - (token.isIdent() && token.value in this.kLIST_STYLE_TYPE_NAMES)) { - lType = token.value; - } - - else if (!lPosition && - (token.isIdent() && token.value in kPosition)) { - lPosition = token.value; - } - - else if (!lImage && token.isFunction("url")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - lImage = "url(" + urlContent; - } - else - return ""; - } - else if (!token.isIdent("none")) - return ""; - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - lType = lType ? lType : "none"; - lImage = lImage ? lImage : "none"; - lPosition = lPosition ? lPosition : "outside"; - - aDecl.push(this._createJscsspDeclarationFromValue("list-style-type", lType)); - aDecl.push(this._createJscsspDeclarationFromValue("list-style-position", lPosition)); - aDecl.push(this._createJscsspDeclarationFromValue("list-style-image", lImage)); - return lType + " " + lPosition + " " + lImage; -}; - -CSSParser.prototype.parse = function(aString, aTryToPreserveWhitespaces, aTryToPreserveComments) { - if (!aString) - return null; // early way out if we can - - this.mPreserveWS = aTryToPreserveWhitespaces; - this.mPreserveComments = aTryToPreserveComments; - this.mPreservedTokens = []; - this.mScanner.init(aString); - var sheet = new jscsspStylesheet(); - - // @charset can only appear at first char of the stylesheet - var token = this.getToken(false, false); - if (!token.isNotNull()) - return sheet; - if (token.isAtRule("@charset")) { - this.ungetToken(); - this.parseCharsetRule(sheet); - token = this.getToken(false, false); - } - - var foundStyleRules = false; - var foundImportRules = false; - var foundNameSpaceRules = false; - while (true) { - if (!token.isNotNull()) - break; - if (token.isWhiteSpace()) - { - if (aTryToPreserveWhitespaces) - this.addWhitespace(sheet, token.value); - } - - else if (token.isComment()) - { - if (this.mPreserveComments) - this.addComment(sheet, token.value); - } - - else if (token.isAtRule()) { - if (token.isAtRule("@variables")) { - if (!foundImportRules && !foundStyleRules) - this.parseVariablesRule(token, sheet); - else { - this.reportError(kVARIABLES_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@import")) { - // @import rules MUST occur before all style and namespace - // rules - if (!foundStyleRules && !foundNameSpaceRules) - foundImportRules = this.parseImportRule(token, sheet); - else { - this.reportError(kIMPORT_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@namespace")) { - // @namespace rules MUST occur before all style rule and - // after all @import rules - if (!foundStyleRules) - foundNameSpaceRules = this.parseNamespaceRule(token, sheet); - else { - this.reportError(kNAMESPACE_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@font-face")) { - if (this.parseFontFaceRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@page")) { - if (this.parsePageRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@media")) { - if (this.parseMediaRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@keyframes")) { - if (!this.parseKeyframesRule(token, sheet)) - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@charset")) { - this.reportError(kCHARSET_RULE_CHARSET_SOF); - this.addUnknownAtRule(sheet, token.value); - } - else { - this.reportError(kUNKNOWN_AT_RULE); - this.addUnknownAtRule(sheet, token.value); - } - } - - else // plain style rules - { - var ruleText = this.parseStyleRule(token, sheet, false); - if (ruleText) - foundStyleRules = true; - } - token = this.getToken(false); - } - - return sheet; -}; -CSSParser.prototype.parseMarginOrPaddingShorthand = function(token, aDecl, aAcceptPriority, aProperty) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - token = this.getToken(true, true); - break; - } - - else if (token.isDimension() - || token.isNumber("0") - || token.isPercentage() - || token.isIdent("auto")) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-top", top)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-right", right)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-bottom", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-left", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseMediaQuery = function() -{ - var kCONSTRAINTS = { - "width": true, - "min-width": true, - "max-width": true, - "height": true, - "min-height": true, - "max-height": true, - "device-width": true, - "min-device-width": true, - "max-device-width": true, - "device-height": true, - "min-device-height": true, - "max-device-height": true, - "orientation": true, - "aspect-ratio": true, - "min-aspect-ratio": true, - "max-aspect-ratio": true, - "device-aspect-ratio": true, - "min-device-aspect-ratio": true, - "max-device-aspect-ratio": true, - "color": true, - "min-color": true, - "max-color": true, - "color-index": true, - "min-color-index": true, - "max-color-index": true, - "monochrome": true, - "min-monochrome": true, - "max-monochrome": true, - "resolution": true, - "min-resolution": true, - "max-resolution": true, - "scan": true, - "grid": true - }; - - var m = {cssText: "", amplifier: "", medium: "", constraints: []}; - var token = this.getToken(true, true); - - if (token.isIdent("all") || - token.isIdent("aural") || - token.isIdent("braille") || - token.isIdent("handheld") || - token.isIdent("print") || - token.isIdent("projection") || - token.isIdent("screen") || - token.isIdent("tty") || - token.isIdent("tv")) { - m.medium = token.value; - m.cssText += token.value; - token = this.getToken(true, true); - } - else if (token.isIdent("not") || token.isIdent("only")) { - m.amplifier = token.value.toLowerCase(); - m.cssText += token.value.toLowerCase(); - token = this.getToken(true, true); - if (token.isIdent("all") || - token.isIdent("aural") || - token.isIdent("braille") || - token.isIdent("handheld") || - token.isIdent("print") || - token.isIdent("projection") || - token.isIdent("screen") || - token.isIdent("tty") || - token.isIdent("tv")) { - m.cssText += " " + token.value; - m.medium = token.value; - token = this.getToken(true, true); - } - else - return null; - } - - if (m.medium) { - if (!token.isNotNull()) - return m; - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else if (!token.isSymbol("{")) - return null; - } - - while (token.isSymbol("(")) { - token = this.getToken(true, true); - if (token.isIdent() && (token.value in kCONSTRAINTS)) { - var constraint = token.value; - token = this.getToken(true, true); - if (token.isSymbol(":")) { - token = this.getToken(true, true); - var values = []; - while (!token.isSymbol(")")) { - values.push(token.value); - token = this.getToken(true, true); - } - if (token.isSymbol(")")) { - m.constraints.push({constraint: constraint, value: values}); - m.cssText += " (" + constraint + ": " + values.join(" ") + ")"; - token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else if (!token.isSymbol("{")) - return null; - } - else - return m; - } - else - return null; - } - else if (token.isSymbol(")")) { - m.constraints.push({constraint: constraint, value: null}); - m.cssText += " (" + constraint + ")"; - token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else - return null; - } - else - return m; - } - else - return null; - } - else - return null; - } - return m; -}; - -CSSParser.prototype.parseMediaRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var mediaRule = new jscsspMediaRule(); - mediaRule.currentLine = currentLine; - this.preserveState(); - var token = this.getToken(true, true); - var foundMedia = false; - while (token.isNotNull()) { - this.ungetToken(); - var mediaQuery = this.parseMediaQuery(); - token = this.currentToken(); - if (mediaQuery) { - foundMedia = true; - s += " " + mediaQuery.cssText; - mediaRule.media.push(mediaQuery.cssText); - if (token.isSymbol(",")) { - s += ","; - } else { - if (token.isSymbol("{")) - break; - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - } - else if (token.isSymbol("{")) - break; - else if (foundMedia) { - token.type = jscsspToken.NULL_TYPE; - // not a media list - break; - } - - token = this.getToken(true, true); - } - if (token.isSymbol("{") && mediaRule.media.length) { - // ok let's parse style rules now... - s += " { "; - token = this.getToken(true, false); - while (token.isNotNull()) { - if (token.isComment()) { - if (this.mPreserveComments) { - s += " " + token.value; - var comment = new jscsspComment(); - comment.parsedCssText = token.value; - mediaRule.cssRules.push(comment); - } - } else if (token.isSymbol("}")) { - valid = true; - break; - } else { - var r = this.parseStyleRule(token, mediaRule, true); - if (r) - s += r; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - mediaRule.parsedCssText = s; - aSheet.cssRules.push(mediaRule); - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseNamespaceRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - this.preserveState(); - var token = this.getToken(true, true); - if (token.isNotNull()) { - var prefix = ""; - var url = ""; - if (token.isIdent()) { - prefix = token.value; - s += " " + prefix; - token = this.getToken(true, true); - } - if (token) { - var foundURL = false; - if (token.isString()) { - foundURL = true; - url = token.value; - s += " " + url; - } else if (token.isFunction("url(")) { - // get a url here... - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - url += "url(" + urlContent; - foundURL = true; - s += " " + urlContent; - } - } - } - if (foundURL) { - token = this.getToken(true, true); - if (token.isSymbol(";")) { - s += ";"; - this.forgetState(); - var rule = new jscsspNamespaceRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.prefix = prefix; - rule.url = url; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - } - - } - this.restoreState(); - this.addUnknownAtRule(aSheet, "@namespace"); - return false; -}; - -CSSParser.prototype.parsePageRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var declarations = []; - this.preserveState(); - var token = this.getToken(true, true); - var pageSelector = ""; - if (token.isSymbol(":") || token.isIdent()) { - if (token.isSymbol(":")) { - pageSelector = ":"; - token = this.getToken(false, false); - } - if (token.isIdent()) { - pageSelector += token.value; - s += " " + pageSelector; - token = this.getToken(true, true); - } - } - if (token.isNotNull()) { - // expecting block start - if (token.isSymbol("{")) { - s += " " + token.value; - var token = this.getToken(true, false); - while (true) { - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aSheet); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspPageRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.pageSelector = pageSelector; - rule.declarations = declarations; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parsePauseShorthand = function(token, declarations, aAcceptPriority) -{ - var before = ""; - var after = ""; - - var values = []; - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isDimensionOfUnit("ms") - || token.isDimensionOfUnit("s") - || token.isPercentage() - || token.isNumber("0")) - values.push(token.value); - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - before = values[0]; - after = before; - break; - case 2: - before = values[0]; - after = values[1]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("pause-before", before)); - aDecl.push(this._createJscsspDeclarationFromValue("pause-after", after)); - return before + " " + after; -}; - -CSSParser.prototype.parseDefaultPropertyValue = function(token, aDecl, aAcceptPriority, descriptor, aSheet) { - var valueText = ""; - var blocks = []; - var foundPriority = false; - var values = []; - while (token.isNotNull()) { - - if ((token.isSymbol(";") - || token.isSymbol("}") - || token.isSymbol("!")) - && !blocks.length) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - if (token.isIdent(this.kINHERIT)) { - if (values.length) { - return ""; - } - else { - valueText = this.kINHERIT; - var value = new jscsspVariable(kJscsspINHERIT_VALUE, aSheet); - values.push(value); - token = this.getToken(true, true); - break; - } - } - else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[")) { - blocks.push(token.value); - } - else if (token.isSymbol("}") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - } - } - } - // XXX must find a better way to store individual values - // probably a |values: []| field holding dimensions, percentages - // functions, idents, numbers and symbols, in that order. - if (token.isFunction()) { - if (token.isFunction("var(")) { - token = this.getToken(true, true); - if (token.isIdent()) { - var name = token.value; - token = this.getToken(true, true); - if (token.isSymbol(")")) { - var value = new jscsspVariable(kJscsspVARIABLE_VALUE, aSheet); - valueText += "var(" + name + ")"; - value.name = name; - values.push(value); - } - else - return ""; - } - else - return ""; - } - else { - var fn = token.value; - token = this.getToken(false, true); - var arg = this.parseFunctionArgument(token); - if (arg) { - valueText += fn + arg; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = fn + arg; - values.push(value); - } - else - return ""; - } - } - else if (token.isSymbol("#")) { - var color = this.parseColor(token); - if (color) { - valueText += color; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = color; - values.push(value); - } - else - return ""; - } - else if (!token.isWhiteSpace() && !token.isSymbol(",")) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = token.value; - values.push(value); - valueText += token.value; - } - else - valueText += token.value; - token = this.getToken(false, true); - } - if (values.length && valueText) { - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValuesArray(descriptor, values, valueText)); - return valueText; - } - return ""; -}; - -CSSParser.prototype.parseSelector = function(aToken, aParseSelectorOnly) { - var s = ""; - var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3 - var isFirstInChain = true; - var token = aToken; - var valid = false; - var combinatorFound = false; - while (true) { - if (!token.isNotNull()) { - if (aParseSelectorOnly) - return {selector: s, specificity: specificity }; - return ""; - } - - if (!aParseSelectorOnly && token.isSymbol("{")) { - // end of selector - valid = !combinatorFound; - // don't unget if invalid since addUnknownRule is going to restore state anyway - if (valid) - this.ungetToken(); - break; - } - - if (token.isSymbol(",")) { // group of selectors - s += token.value; - isFirstInChain = true; - combinatorFound = false; - token = this.getToken(false, true); - continue; - } - // now combinators and grouping... - else if (!combinatorFound - && (token.isWhiteSpace() - || token.isSymbol(">") - || token.isSymbol("+") - || token.isSymbol("~"))) { - if (token.isWhiteSpace()) { - s += " "; - var nextToken = this.lookAhead(true, true); - if (!nextToken.isNotNull()) { - if (aParseSelectorOnly) - return {selector: s, specificity: specificity }; - return ""; - } - if (nextToken.isSymbol(">") - || nextToken.isSymbol("+") - || nextToken.isSymbol("~")) { - token = this.getToken(true, true); - s += token.value + " "; - combinatorFound = true; - } - } - else { - s += token.value; - combinatorFound = true; - } - isFirstInChain = true; - token = this.getToken(true, true); - continue; - } - else { - var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, true); - if (!simpleSelector) - break; // error - s += simpleSelector.selector; - specificity.b += simpleSelector.specificity.b; - specificity.c += simpleSelector.specificity.c; - specificity.d += simpleSelector.specificity.d; - isFirstInChain = false; - combinatorFound = false; - } - - token = this.getToken(false, true); - } - - if (valid) { - return {selector: s, specificity: specificity }; - } - return ""; -}; - -CSSParser.prototype.isPseudoElement = function(aIdent) -{ - switch (aIdent) { - case "first-letter": - case "first-line": - case "before": - case "after": - case "marker": - return true; - break; - default: - break; - } - return false; -}; - -CSSParser.prototype.parseSimpleSelector = function(token, isFirstInChain, canNegate) -{ - var s = ""; - var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3 - - if (isFirstInChain - && (token.isSymbol("*") || token.isSymbol("|") || token.isIdent())) { - // type or universal selector - if (token.isSymbol("*") || token.isIdent()) { - // we don't know yet if it's a prefix or a universal - // selector - s += token.value; - var isIdent = token.isIdent(); - token = this.getToken(false, true); - if (token.isSymbol("|")) { - // it's a prefix - s += token.value; - token = this.getToken(false, true); - if (token.isIdent() || token.isSymbol("*")) { - // ok we now have a type element or universal - // selector - s += token.value; - if (token.isIdent()) - specificity.d++; - } else - // oops that's an error... - return null; - } else { - this.ungetToken(); - if (isIdent) - specificity.d++; - } - } else if (token.isSymbol("|")) { - s += token.value; - token = this.getToken(false, true); - if (token.isIdent() || token.isSymbol("*")) { - s += token.value; - if (token.isIdent()) - specificity.d++; - } else - // oops that's an error - return null; - } - } - - else if (token.isSymbol(".") || token.isSymbol("#")) { - var isClass = token.isSymbol("."); - s += token.value; - token = this.getToken(false, true); - if (token.isIdent()) { - s += token.value; - if (isClass) - specificity.c++; - else - specificity.b++; - } - else - return null; - } - - else if (token.isSymbol(":")) { - s += token.value; - token = this.getToken(false, true); - if (token.isSymbol(":")) { - s += token.value; - token = this.getToken(false, true); - } - if (token.isIdent()) { - s += token.value; - if (this.isPseudoElement(token.value)) - specificity.d++; - else - specificity.c++; - } - else if (token.isFunction()) { - s += token.value; - if (token.isFunction(":not(")) { - if (!canNegate) - return null; - token = this.getToken(true, true); - var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, false); - if (!simpleSelector) - return null; - else { - s += simpleSelector.selector; - token = this.getToken(true, true); - if (token.isSymbol(")")) - s += ")"; - else - return null; - } - specificity.c++; - } - else { - while (true) { - token = this.getToken(false, true); - if (token.isSymbol(")")) { - s += ")"; - break; - } else - s += token.value; - } - specificity.c++; - } - } else - return null; - - } else if (token.isSymbol("[")) { - s += "["; - token = this.getToken(true, true); - if (token.isIdent() || token.isSymbol("*")) { - s += token.value; - var nextToken = this.getToken(true, true); - if (nextToken.isSymbol("|")) { - s += "|"; - token = this.getToken(true, true); - if (token.isIdent()) - s += token.value; - else - return null; - } else - this.ungetToken(); - } else if (token.isSymbol("|")) { - s += "|"; - token = this.getToken(true, true); - if (token.isIdent()) - s += token.value; - else - return null; - } - else - return null; - - // nothing, =, *=, $=, ^=, |= - token = this.getToken(true, true); - if (token.isIncludes() - || token.isDashmatch() - || token.isBeginsmatch() - || token.isEndsmatch() - || token.isContainsmatch() - || token.isSymbol("=")) { - s += token.value; - token = this.getToken(true, true); - if (token.isString() || token.isIdent()) { - s += token.value; - token = this.getToken(true, true); - } - else - return null; - - if (token.isSymbol("]")) { - s += token.value; - specificity.c++; - } - else - return null; - } - else if (token.isSymbol("]")) { - s += token.value; - specificity.c++; - } - else - return null; - - } - else if (token.isWhiteSpace()) { - var t = this.lookAhead(true, true); - if (t.isSymbol('{')) - return "" - } - if (s) - return {selector: s, specificity: specificity }; - return null; -}; - -CSSParser.prototype.trim11 = function(str) { - str = str.replace(/^\s+/, ''); - for (var i = str.length - 1; i >= 0; i--) { - if (/\S/.test( str.charAt(i) )) { // XXX charat - str = str.substring(0, i + 1); - break; - } - } - return str; -}; - -CSSParser.prototype.parseStyleRule = function(aToken, aOwner, aIsInsideMediaRule) -{ - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - this.preserveState(); - // first let's see if we have a selector here... - var selector = this.parseSelector(aToken, false); - var valid = false; - var declarations = []; - if (selector) { - selector = this.trim11(selector.selector); - var s = selector; - var token = this.getToken(true, true); - if (token.isSymbol("{")) { - s += " { "; - var token = this.getToken(true, false); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aOwner); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - else { - // selector is invalid so the whole rule is invalid with it - } - - if (valid) { - var rule = new jscsspStyleRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.mSelectorText = selector; - if (aIsInsideMediaRule) - rule.parentRule = aOwner; - else - rule.parentStyleSheet = aOwner; - aOwner.cssRules.push(rule); - return s; - } - this.restoreState(); - s = this.currentToken().value; - this.addUnknownAtRule(aOwner, s); - return ""; -}; - -CSSParser.prototype.parseTextShadows = function() -{ - var shadows = []; - var token = this.getToken(true, true); - var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px"; - while (token.isNotNull()) { - if (token.isIdent("none")) { - shadows.push( { none: true } ); - token = this.getToken(true, true); - } - else { - if (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent()) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetX = token.value; - token = this.getToken(true, true); - } - else - return []; - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetY = token.value; - token = this.getToken(true, true); - } - else - return []; - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var blurRadius = token.value; - token = this.getToken(true, true); - } - if (!color && - (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent())) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - - shadows.push( { none: false, - color: color, - offsetX: offsetX, offsetY: offsetY, - blurRadius: blurRadius } ); - - if (token.isSymbol(",")) { - color = ""; - blurRadius = "0px"; - offsetX = "0px"; - offsetY = "0px"; - token = this.getToken(true, true); - } - else if (!token.isNotNull()) - return shadows; - else - return []; - } - } - return shadows; -}; - -CSSParser.prototype.currentToken = function() { - return this.mToken; -}; - -CSSParser.prototype.getHexValue = function() { - this.mToken = this.mScanner.nextHexValue(); - return this.mToken; -}; - -CSSParser.prototype.getToken = function(aSkipWS, aSkipComment) { - if (this.mLookAhead) { - this.mToken = this.mLookAhead; - this.mLookAhead = null; - return this.mToken; - } - - this.mToken = this.mScanner.nextToken(); - while (this.mToken && - ((aSkipWS && this.mToken.isWhiteSpace()) || - (aSkipComment && this.mToken.isComment()))) - this.mToken = this.mScanner.nextToken(); - return this.mToken; -}; - -CSSParser.prototype.lookAhead = function(aSkipWS, aSkipComment) { - var preservedToken = this.mToken; - this.mScanner.preserveState(); - var token = this.getToken(aSkipWS, aSkipComment); - this.mScanner.restoreState(); - this.mToken = preservedToken; - - return token; -}; - -CSSParser.prototype.ungetToken = function() { - this.mLookAhead = this.mToken; -}; - -CSSParser.prototype.addWhitespace = function(aSheet, aString) { - var rule = new jscsspWhitespace(); - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype.addComment = function(aSheet, aString) { - var rule = new jscsspComment(); - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype._createJscsspDeclaration = function(property, value) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - decl.value = this.trim11(value); - decl.parsedCssText = property + ": " + value + ";"; - return decl; -}; - -CSSParser.prototype._createJscsspDeclarationFromValue = function(property, valueText) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = valueText; - decl.values = [value]; - decl.valueText = valueText; - decl.parsedCssText = property + ": " + valueText + ";"; - return decl; -}; - -CSSParser.prototype._createJscsspDeclarationFromValuesArray = function(property, values, valueText) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - decl.values = values; - decl.valueText = valueText; - decl.parsedCssText = property + ": " + valueText + ";"; - return decl; -}; - -CSSParser.prototype.preserveState = function() { - this.mPreservedTokens.push(this.currentToken()); - this.mScanner.preserveState(); -}; - -CSSParser.prototype.restoreState = function() { - if (this.mPreservedTokens.length) { - this.mScanner.restoreState(); - this.mToken = this.mPreservedTokens.pop(); - } -}; - -CSSParser.prototype.forgetState = function() { - if (this.mPreservedTokens.length) { - this.mScanner.forgetState(); - this.mPreservedTokens.pop(); - } -}; - -CSSParser.prototype.addUnknownAtRule = function(aSheet, aString) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var blocks = []; - var token = this.getToken(false, false); - while (token.isNotNull()) { - aString += token.value; - if (token.isSymbol(";") && !blocks.length) - break; - else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[") - || token.type == "function") { - blocks.push(token.isFunction() ? "(" : token.value); - } else if (token.isSymbol("}") - || token.isSymbol(")") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - if (!blocks.length && token.isSymbol("}")) - break; - } - } - } - token = this.getToken(false, false); - } - - this.addUnknownRule(aSheet, aString, currentLine); -}; - -CSSParser.prototype.addUnknownRule = function(aSheet, aString, aCurrentLine) { - var errorMsg = this.consumeError(); - var rule = new jscsspErrorRule(errorMsg); - rule.currentLine = aCurrentLine; - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype.parseURL = function(token) -{ - var value = ""; - if (token.isString()) - { - value += token.value; - token = this.getToken(true, true); - } - else - while (true) - { - if (!token.isNotNull()) { - this.reportError(kURL_EOF); - return ""; - } - if (token.isWhiteSpace()) { - nextToken = this.lookAhead(true, true); - // if next token is not a closing parenthesis, that's an error - if (!nextToken.isSymbol(")")) { - this.reportError(kURL_WS_INSIDE); - token = this.currentToken(); - break; - } - } - if (token.isSymbol(")")) { - break; - } - value += token.value; - token = this.getToken(false, false); - } - - if (token.isSymbol(")")) { - return value + ")"; - } - return ""; -}; - -CSSParser.prototype.parseVariablesRule = function(token, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = token.value; - var declarations = []; - var valid = false; - this.preserveState(); - token = this.getToken(true, true); - var media = []; - var foundMedia = false; - while (token.isNotNull()) { - if (token.isIdent()) { - foundMedia = true; - s += " " + token.value; - media.push(token.value); - token = this.getToken(true, true); - if (token.isSymbol(",")) { - s += ","; - } else { - if (token.isSymbol("{")) - this.ungetToken(); - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - } else if (token.isSymbol("{")) - break; - else if (foundMedia) { - token.type = jscsspToken.NULL_TYPE; - // not a media list - break; - } - token = this.getToken(true, true); - } - - if (token.isSymbol("{")) { - s += " {"; - token = this.getToken(true, true); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, false, aSheet); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspVariablesRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.media = media; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -function jscsspToken(aType, aValue, aUnit) -{ - this.type = aType; - this.value = aValue; - this.unit = aUnit; -} - -jscsspToken.NULL_TYPE = 0; - -jscsspToken.WHITESPACE_TYPE = 1; -jscsspToken.STRING_TYPE = 2; -jscsspToken.COMMENT_TYPE = 3; -jscsspToken.NUMBER_TYPE = 4; -jscsspToken.IDENT_TYPE = 5; -jscsspToken.FUNCTION_TYPE = 6; -jscsspToken.ATRULE_TYPE = 7; -jscsspToken.INCLUDES_TYPE = 8; -jscsspToken.DASHMATCH_TYPE = 9; -jscsspToken.BEGINSMATCH_TYPE = 10; -jscsspToken.ENDSMATCH_TYPE = 11; -jscsspToken.CONTAINSMATCH_TYPE = 12; -jscsspToken.SYMBOL_TYPE = 13; -jscsspToken.DIMENSION_TYPE = 14; -jscsspToken.PERCENTAGE_TYPE = 15; -jscsspToken.HEX_TYPE = 16; - -jscsspToken.prototype = { - - isNotNull: function () - { - return this.type; - }, - - _isOfType: function (aType, aValue) - { - return (this.type == aType && (!aValue || this.value.toLowerCase() == aValue)); - }, - - isWhiteSpace: function(w) - { - return this._isOfType(jscsspToken.WHITESPACE_TYPE, w); - }, - - isString: function() - { - return this._isOfType(jscsspToken.STRING_TYPE); - }, - - isComment: function() - { - return this._isOfType(jscsspToken.COMMENT_TYPE); - }, - - isNumber: function(n) - { - return this._isOfType(jscsspToken.NUMBER_TYPE, n); - }, - - isIdent: function(i) - { - return this._isOfType(jscsspToken.IDENT_TYPE, i); - }, - - isFunction: function(f) - { - return this._isOfType(jscsspToken.FUNCTION_TYPE, f); - }, - - isAtRule: function(a) - { - return this._isOfType(jscsspToken.ATRULE_TYPE, a); - }, - - isIncludes: function() - { - return this._isOfType(jscsspToken.INCLUDES_TYPE); - }, - - isDashmatch: function() - { - return this._isOfType(jscsspToken.DASHMATCH_TYPE); - }, - - isBeginsmatch: function() - { - return this._isOfType(jscsspToken.BEGINSMATCH_TYPE); - }, - - isEndsmatch: function() - { - return this._isOfType(jscsspToken.ENDSMATCH_TYPE); - }, - - isContainsmatch: function() - { - return this._isOfType(jscsspToken.CONTAINSMATCH_TYPE); - }, - - isSymbol: function(c) - { - return this._isOfType(jscsspToken.SYMBOL_TYPE, c); - }, - - isDimension: function() - { - return this._isOfType(jscsspToken.DIMENSION_TYPE); - }, - - isPercentage: function() - { - return this._isOfType(jscsspToken.PERCENTAGE_TYPE); - }, - - isHex: function() - { - return this._isOfType(jscsspToken.HEX_TYPE); - }, - - isDimensionOfUnit: function(aUnit) - { - return (this.isDimension() && this.unit == aUnit); - }, - - isLength: function() - { - return (this.isPercentage() || - this.isDimensionOfUnit("cm") || - this.isDimensionOfUnit("mm") || - this.isDimensionOfUnit("in") || - this.isDimensionOfUnit("pc") || - this.isDimensionOfUnit("px") || - this.isDimensionOfUnit("em") || - this.isDimensionOfUnit("ex") || - this.isDimensionOfUnit("pt")); - }, - - isAngle: function() - { - return (this.isDimensionOfUnit("deg") || - this.isDimensionOfUnit("rad") || - this.isDimensionOfUnit("grad")); - } -} - -/* kJscsspCHARSET_RULE */ - -function jscsspCharsetRule() -{ - this.type = kJscsspCHARSET_RULE; - this.encoding = null; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspCharsetRule.prototype = { - - cssText: function() { - return "@charset " + this.encoding + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - if (parser.parseCharsetRule(sheet)) { - var newRule = sheet.cssRules[0]; - this.encoding = newRule.encoding; - this.parsedCssText = newRule.parsedCssText; - return; - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspCOMMENT */ - -function jscsspComment() -{ - this.type = kJscsspCOMMENT; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspComment.prototype = { - cssText: function() { - return this.parsedCssText; - }, - - setCssText: function(val) { - var parser = new CSSParser(val); - var token = parser.getToken(true, false); - if (token.isComment()) - this.parsedCssText = token.value; - else - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspSTYLE_DECLARATION */ - -function jscsspDeclaration() -{ - this.type = kJscsspSTYLE_DECLARATION; - this.property = null; - this.values = []; - this.valueText = null; - this.priority = null; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspDeclaration.prototype = { - kCOMMA_SEPARATED: { - "cursor": true, - "font-family": true, - "voice-family": true, - "background-image": true - }, - - kUNMODIFIED_COMMA_SEPARATED_PROPERTIES: { - "text-shadow": true, - "box-shadow": true, - "-moz-transition": true, - "-moz-transition-property": true, - "-moz-transition-duration": true, - "-moz-transition-timing-function": true, - "-moz-transition-delay": true, - "src": true, - "-moz-font-feature-settings": true - }, - - cssText: function() { - var prefixes = PrefixHelper.prefixesForProperty(this.property); - - var rv = ""; - if (this.property in this.kUNMODIFIED_COMMA_SEPARATED_PROPERTIES) { - if (prefixes) { - rv = ""; - for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) { - var property = prefixes[propertyIndex]; - rv += (propertyIndex ? gTABS : "") + property + ": "; - rv += this.valueText + (this.priority ? " !important" : "") + ";"; - rv += ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : ""); - } - return rv; - } - return this.property + ": " + this.valueText + - (this.priority ? " !important" : "") + ";" - } - - if (prefixes) { - rv = ""; - for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) { - var property = prefixes[propertyIndex]; - rv += (propertyIndex ? gTABS : "") + property + ": "; - var separator = (property in this.kCOMMA_SEPARATED) ? ", " : " "; - for (var i = 0; i < this.values.length; i++) - if (this.values[i].cssText() != null) - rv += (i ? separator : "") + this.values[i].cssText(); - else - return null; - rv += (this.priority ? " !important" : "") + ";" + - ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : ""); - } - return rv; - } - - var separator = (this.property in this.kCOMMA_SEPARATED) ? ", " : " "; - var extras = {"webkit": false, "presto": false, "trident": false, "gecko1.9.2": false, "generic": false } - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - var paren = v.indexOf("("); - var kwd = v; - if (paren != -1) - kwd = v.substr(0, paren); - if (kwd in kCSS_VENDOR_VALUES) { - for (var j in kCSS_VENDOR_VALUES[kwd]) { - extras[j] = extras[j] || (kCSS_VENDOR_VALUES[kwd][j] != ""); - } - } - } - else - return null; - } - - for (var j in extras) { - if (extras[j]) { - var str = "\n" + gTABS + this.property + ": "; - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - var paren = v.indexOf("("); - var kwd = v; - if (paren != -1) - kwd = v.substr(0, paren); - if (kwd in kCSS_VENDOR_VALUES) { - functor = kCSS_VENDOR_VALUES[kwd][j]; - if (functor) { - v = (typeof functor == "string") ? functor : functor(v, j); - if (!v) { - str = null; - break; - } - } - } - str += (i ? separator : "") + v; - } - else - return null; - } - if (str) - rv += str + ";" - else - rv += "\n" + gTABS + "/* Impossible to translate property " + this.property + " for " + j + " */"; - } - } - - rv += "\n" + gTABS + this.property + ": "; - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - rv += (i ? separator : "") + v; - } - } - rv += (this.priority ? " !important" : "") + ";"; - - return rv; - }, - - setCssText: function(val) { - var declarations = []; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (parser.parseDeclaration(token, declarations, true, true, null) - && declarations.length - && declarations[0].type == kJscsspSTYLE_DECLARATION) { - var newDecl = declarations.cssRules[0]; - this.property = newDecl.property; - this.value = newDecl.value; - this.priority = newDecl.priority; - this.parsedCssText = newRule.parsedCssText; - return; - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspUNKNOWN_RULE */ - -function jscsspErrorRule(aErrorMsg) -{ - this.error = aErrorMsg ? aErrorMsg : "INVALID"; - this.type = kJscsspUNKNOWN_RULE; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspErrorRule.prototype = { - cssText: function() { - return this.parsedCssText; - } -}; - -/* kJscsspFONT_FACE_RULE */ - -function jscsspFontFaceRule() -{ - this.type = kJscsspFONT_FACE_RULE; - this.parsedCssText = null; - this.descriptors = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspFontFaceRule.prototype = { - cssText: function() { - var rv = gTABS + "@font-face {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.descriptors.length; i++) - rv += gTABS + this.descriptors[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@font-face")) { - if (parser.parseFontFaceRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.descriptors = newRule.descriptors; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspIMPORT_RULE */ - -function jscsspImportRule() -{ - this.type = kJscsspIMPORT_RULE; - this.parsedCssText = null; - this.href = null; - this.media = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspImportRule.prototype = { - cssText: function() { - var mediaString = this.media.join(", "); - return "@import " + this.href - + ((mediaString && mediaString != "all") ? mediaString + " " : "") - + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@import")) { - if (parser.parseImportRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.href = newRule.href; - this.media = newRule.media; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspKEYFRAME_RULE */ -function jscsspKeyframeRule() -{ - this.type = kJscsspKEYFRAME_RULE; - this.parsedCssText = null; - this.declarations = [] - this.keyText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspKeyframeRule.prototype = { - cssText: function() { - var rv = this.keyText + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) { - var declText = this.declarations[i].cssText(); - if (declText) - rv += gTABS + this.declarations[i].cssText() + "\n"; - } - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - if (parser.parseKeyframeRule(token, sheet, false)) { - var newRule = sheet.cssRules[0]; - this.keyText = newRule.keyText; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspKEYFRAMES_RULE */ -function jscsspKeyframesRule() -{ - this.type = kJscsspKEYFRAMES_RULE; - this.parsedCssText = null; - this.cssRules = []; - this.name = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspKeyframesRule.prototype = { - cssText: function() { - var rv = ""; - var prefixes = ["moz", "webkit", "ms", "o", ""]; - for (var p = 0; p < prefixes.length; p++) { - rv += gTABS - + "@" + (prefixes[p] ? "-" + prefixes[p] + "-" : "") + "keyframes " - + this.name + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.cssRules.length; i++) - rv += gTABS + this.cssRules[i].cssText() + "\n"; - gTABS = preservedGTABS; - rv += gTABS + "}\n\n"; - } - return rv; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@-mozkeyframes")) { - if (parser.parseKeyframesRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.cssRules = newRule.cssRules; - this.name = newRule.name; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspMEDIA_RULE */ - -function jscsspMediaRule() -{ - this.type = kJscsspMEDIA_RULE; - this.parsedCssText = null; - this.cssRules = []; - this.media = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspMediaRule.prototype = { - cssText: function() { - var rv = gTABS + "@media " + this.media.join(", ") + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.cssRules.length; i++) - rv += this.cssRules[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@media")) { - if (parser.parseMediaRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.cssRules = newRule.cssRules; - this.media = newRule.media; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspNAMESPACE_RULE */ - -function jscsspNamespaceRule() -{ - this.type = kJscsspNAMESPACE_RULE; - this.parsedCssText = null; - this.prefix = null; - this.url = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspNamespaceRule.prototype = { - cssText: function() { - return "@namespace " + (this.prefix ? this.prefix + " ": "") - + this.url - + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@namespace")) { - if (parser.parseNamespaceRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.url = newRule.url; - this.prefix = newRule.prefix; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspPAGE_RULE */ - -function jscsspPageRule() -{ - this.type = kJscsspPAGE_RULE; - this.parsedCssText = null; - this.pageSelector = null; - this.declarations = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspPageRule.prototype = { - cssText: function() { - var rv = gTABS + "@page " - + (this.pageSelector ? this.pageSelector + " ": "") - + "{\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) - rv += gTABS + this.declarations[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@page")) { - if (parser.parsePageRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.pageSelector = newRule.pageSelector; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspSTYLE_RULE */ - -function jscsspStyleRule() -{ - this.type = kJscsspSTYLE_RULE; - this.parsedCssText = null; - this.declarations = [] - this.mSelectorText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspStyleRule.prototype = { - cssText: function() { - var rv = gTABS + this.mSelectorText + " {"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) { - var declText = this.declarations[i].cssText(); - if (declText) - rv += gTABS + this.declarations[i].cssText() ; - } - gTABS = preservedGTABS; - return rv + "\n" + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - if (parser.parseStyleRule(token, sheet, false)) { - var newRule = sheet.cssRules[0]; - this.mSelectorText = newRule.mSelectorText; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - }, - - selectorText: function() { - return this.mSelectorText; - }, - - setSelectorText: function(val) { - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - var s = parser.parseSelector(token, true); - if (s) { - this.mSelectorText = s.selector; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -function jscsspStylesheet() -{ - this.cssRules = []; - this.variables = {}; -} - -jscsspStylesheet.prototype = { - insertRule: function(aRule, aIndex) { - try { - this.cssRules.splice(aIndex, 1, aRule); - } - catch(e) { - } - }, - - deleteRule: function(aIndex) { - try { - this.cssRules.splice(aIndex); - } - catch(e) { - } - }, - - cssText: function() { - var rv = ""; - for (var i = 0; i < this.cssRules.length; i++) - rv += this.cssRules[i].cssText() + "\n\n"; - return rv; - } -}; - -var kJscsspINHERIT_VALUE = 0; -var kJscsspPRIMITIVE_VALUE = 1; -var kJscsspVARIABLE_VALUE = 4; - -function jscsspVariable(aType, aSheet) -{ - this.value = ""; - this.type = aType; - this.name = null; - this.parentRule = null; - this.parentStyleSheet = aSheet; -} - -jscsspVariable.prototype = { - cssText: function() { - if (this.type == kJscsspVARIABLE_VALUE) - return this.resolveVariable(this.name, this.parentRule, this.parentStyleSheet); - else - return this.value; - }, - - setCssText: function(val) { - if (this.type == kJscsspVARIABLE_VALUE) - throw DOMException.SYNTAX_ERR; - else - this.value = val; - }, - - resolveVariable: function(aName, aRule, aSheet) - { - return "var(" + aName + ")"; - } -}; - -/* kJscsspVARIABLES_RULE */ - -function jscsspVariablesRule() -{ - this.type = kJscsspVARIABLES_RULE; - this.parsedCssText = null; - this.declarations = []; - this.parentStyleSheet = null; - this.parentRule = null; - this.media = null; -} - -jscsspVariablesRule.prototype = { - cssText: function() { - var rv = gTABS + "@variables " + - (this.media.length ? this.media.join(", ") + " " : "") + - "{\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) - rv += gTABS + this.declarations[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@variables")) { - if (parser.parseVariablesRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspWHITE_SPACE */ - -function jscsspWhitespace() -{ - this.type = kJscsspWHITE_SPACE; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspWhitespace.prototype = { - cssText: function() { - return this.parsedCssText; - } -}; - -var kJscsspUNKNOWN_RULE = 0; -var kJscsspSTYLE_RULE = 1 -var kJscsspCHARSET_RULE = 2; -var kJscsspIMPORT_RULE = 3; -var kJscsspMEDIA_RULE = 4; -var kJscsspFONT_FACE_RULE = 5; -var kJscsspPAGE_RULE = 6; - -var kJscsspKEYFRAMES_RULE = 7; -var kJscsspKEYFRAME_RULE = 8; - -var kJscsspNAMESPACE_RULE = 100; -var kJscsspCOMMENT = 101; -var kJscsspWHITE_SPACE = 102; - -var kJscsspVARIABLES_RULE = 200; - -var kJscsspSTYLE_DECLARATION = 1000; - -var gTABS = ""; - diff --git a/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js b/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js deleted file mode 100644 index 3cecd957dc..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * LESS - Leaner CSS v1.7.0 - * http://lesscss.org - * - * Copyright (c) 2009-2014, Alexis Sellier - * Licensed under the Apache v2 License. - * - */ - - /** * @license Apache v2 - */ - -!function(a,b){function c(b){return a.less[b.split("/")[1]]}function d(a,b){"undefined"!=typeof console&&w.logLevel>=b&&console.log("less: "+a)}function e(a){return a.replace(/^[a-z-]+:\/+?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function f(a,c){var e="{line} {content}",f=a.filename||c,g=[],h=(a.type||"Syntax")+"Error: "+(a.message||"There is an error in your .less file")+" in "+f+" ",i=function(a,c,d){a.extract[c]!==b&&g.push(e.replace(/\{line\}/,(parseInt(a.line,10)||0)+(c-1)).replace(/\{class\}/,d).replace(/\{content\}/,a.extract[c]))};a.extract?(i(a,0,""),i(a,1,"line"),i(a,2,""),h+="on line "+a.line+", column "+(a.column+1)+":\n"+g.join("\n")):a.stack&&(h+=a.stack),d(h,z.errors)}function g(a,b,c){var f=b.href||"",g="less:"+(b.title||e(f)),h=document.getElementById(g),i=!1,j=document.createElement("style");if(j.setAttribute("type","text/css"),b.media&&j.setAttribute("media",b.media),j.id=g,j.styleSheet)try{j.styleSheet.cssText=a}catch(k){throw new Error("Couldn't reassign styleSheet.cssText.")}else j.appendChild(document.createTextNode(a)),i=null!==h&&h.childNodes.length>0&&j.childNodes.length>0&&h.firstChild.nodeValue===j.firstChild.nodeValue;var l=document.getElementsByTagName("head")[0];if(null===h||i===!1){var m=b&&b.nextSibling||null;m?m.parentNode.insertBefore(j,m):l.appendChild(j)}if(h&&i===!1&&h.parentNode.removeChild(h),c&&D){d("saving "+f+" to cache.",z.info);try{D.setItem(f,a),D.setItem(f+":timestamp",c)}catch(k){d("failed to save",z.errors)}}}function h(a){return w.postProcessor&&"function"==typeof w.postProcessor&&(a=w.postProcessor.call(a,a)||a),a}function i(a,c){var d,f,h="less-error-message:"+e(c||""),i='
  • {content}
  • ',j=document.createElement("div"),k=[],l=a.filename||c,m=l.match(/([^\/]+(\?.*)?)$/)[1];j.id=h,j.className="less-error-message",f="

    "+(a.type||"Syntax")+"Error: "+(a.message||"There is an error in your .less file")+'

    in '+m+" ";var n=function(a,c,d){a.extract[c]!==b&&k.push(i.replace(/\{line\}/,(parseInt(a.line,10)||0)+(c-1)).replace(/\{class\}/,d).replace(/\{content\}/,a.extract[c]))};a.extract?(n(a,0,""),n(a,1,"line"),n(a,2,""),f+="on line "+a.line+", column "+(a.column+1)+":

      "+k.join("")+"
    "):a.stack&&(f+="
    "+a.stack.split("\n").slice(1).join("
    ")),j.innerHTML=f,g([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),j.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),"development"==w.env&&(d=setInterval(function(){document.body&&(document.getElementById(h)?document.body.replaceChild(j,document.getElementById(h)):document.body.insertBefore(j,document.body.firstChild),clearInterval(d))},10))}function j(a,b){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?f(a,b):"function"==typeof w.errorReporting&&w.errorReporting("add",a,b):i(a,b)}function k(a){var b=document.getElementById("less-error-message:"+e(a));b&&b.parentNode.removeChild(b)}function l(){}function m(a){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?l(a):"function"==typeof w.errorReporting&&w.errorReporting("remove",a):k(a)}function n(a){for(var b,c=document.getElementsByTagName("style"),d=0;d0&&(h.splice(c-1,2),c-=2)}return g.hostPart=f[1],g.directories=h,g.path=f[1]+h.join("/"),g.fileUrl=g.path+(f[4]||""),g.url=g.fileUrl+(f[5]||""),g}function p(a,b){var c,d,e,f,g=o(a),h=o(b),i="";if(g.hostPart!==h.hostPart)return"";for(d=Math.max(h.directories.length,g.directories.length),c=0;d>c&&h.directories[c]===g.directories[c];c++);for(f=h.directories.slice(c),e=g.directories.slice(c),c=0;c=200&&b.status<300?c(b.responseText,b.getResponseHeader("Last-Modified")):"function"==typeof d&&d(b.status,a)}var g=q(),h=y?w.fileAsync:w.async;"function"==typeof g.overrideMimeType&&g.overrideMimeType("text/css"),d("XHR: Getting '"+a+"'",z.debug),g.open("GET",a,h),g.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5"),g.send(null),y&&!w.fileAsync?0===g.status||g.status>=200&&g.status<300?c(g.responseText):e(g.status,a):h?g.onreadystatechange=function(){4==g.readyState&&f(g,c,e)}:f(g,c,e)}function s(b,c,d,e){c&&c.currentDirectory&&!/^([a-z-]+:)?\//.test(b)&&(b=c.currentDirectory+b);var f=o(b,a.location.href),g=f.url,h={currentDirectory:f.path,filename:g};if(c?(h.entryPath=c.entryPath,h.rootpath=c.rootpath,h.rootFilename=c.rootFilename,h.relativeUrls=c.relativeUrls):(h.entryPath=f.path,h.rootpath=w.rootpath||f.path,h.rootFilename=g,h.relativeUrls=e.relativeUrls),h.relativeUrls&&(h.rootpath=e.rootpath?o(e.rootpath+p(f.path,h.entryPath)).path:f.path),e.useFileCache&&E[g])try{var i=E[g];d(null,i,g,h,{lastModified:new Date})}catch(j){d(j,null,g)}else r(g,e.mime,function(a,b){E[g]=a;try{d(null,a,g,h,{lastModified:b})}catch(c){d(c,null,g)}},function(a,b){d({type:"File",message:"'"+b+"' wasn't found ("+a+")"},null,g)})}function t(a,b,c,d,e){var f=new w.tree.parseEnv(w);f.mime=a.type,(e||w.globalVars)&&(f.useFileCache=!0),s(a.href,null,function(h,i,j,k,l){if(l){l.remaining=d;var n=D&&D.getItem(j),o=D&&D.getItem(j+":timestamp");if(!c&&o&&l.lastModified&&new Date(l.lastModified).valueOf()===new Date(o).valueOf())return g(n,a),l.local=!0,void b(null,null,i,a,l,j)}m(j),i?(f.currentFileInfo=k,new w.Parser(f).parse(i,function(c,d){if(c)return b(c,null,null,a);try{b(c,d,i,a,l,j)}catch(c){b(c,null,null,a)}},{modifyVars:e,globalVars:w.globalVars})):b(h,null,null,a,l,j)},f,e)}function u(a,b,c){for(var d=0;dD&&(C=C.slice(y-D),D=y)}function h(a,b){var c=a.charCodeAt(0|b);return 32>=c&&(32===c||10===c||9===c)}function i(a){var b,c,d=typeof a;return"string"===d?v.charAt(y)!==a?null:(l(1),a):(g(),(b=a.exec(C))?(c=b[0].length,l(c),"string"==typeof b?b:1===b.length?b[0]:b):null)}function j(a){y>D&&(C=C.slice(y-D),D=y);var b=a.exec(C);return b?(l(b[0].length),"string"==typeof b?b:1===b.length?b[0]:b):null}function k(a){return v.charAt(y)!==a?null:(l(1),a)}function l(a){for(var b,c=y,d=z,e=y-D,f=y+C.length-e,g=y+=a,h=v;f>y&&(b=h.charCodeAt(y),!(b>32))&&(32===b||10===b||9===b||13===b);y++);return C=C.slice(a+y-g+e),D=y,!C.length&&z=0&&"\n"!==b.charAt(c);)e++;return"number"==typeof a&&(d=(b.slice(0,a).match(/\n/g)||"").length),{line:d,column:e}}function t(a,b,d){var e=d.currentFileInfo.filename;return"browser"!==w.mode&&"rhino"!==w.mode&&(e=c("path").resolve(e)),{lineNumber:s(a,b).line+1,fileName:e}}function u(a,b){var c=r(a,b),d=s(a.index,c),e=d.line,f=d.column,g=a.call&&s(a.call,c).line,h=c.split("\n");this.type=a.type||"Syntax",this.message=a.message,this.filename=a.filename||b.currentFileInfo.filename,this.index=a.index,this.line="number"==typeof e?e+1:null,this.callLine=g+1,this.callExtract=h[g],this.stack=a.stack,this.column=f,this.extract=[h[e-1],h[e],h[e+1]]}var v,y,z,A,B,C,D,E,F,G=[],H=a&&a.filename;a instanceof x.parseEnv||(a=new x.parseEnv(a));var I=this.imports={paths:a.paths||[],queue:[],files:a.files,contents:a.contents,contentsIgnoredChars:a.contentsIgnoredChars,mime:a.mime,error:null,push:function(b,c,d,e){var f=this;this.queue.push(b);var g=function(a,c,d){f.queue.splice(f.queue.indexOf(b),1);var g=d===H;f.files[d]=c,a&&!f.error&&(f.error=a),e(a,c,g,d)};w.Parser.importer?w.Parser.importer(b,c,g,a):w.Parser.fileLoader(b,c,function(b,e,f,h){if(b)return void g(b);var i=new x.parseEnv(a);i.currentFileInfo=h,i.processImports=!1,i.contents[f]=e,(c.reference||d.reference)&&(h.reference=!0),d.inline?g(null,e,f):new w.Parser(i).parse(e,function(a,b){g(a,b,f)})},a)}},J=j;return u.prototype=new Error,u.prototype.constructor=u,this.env=a=a||{},this.optimization="optimization"in this.env?this.env.optimization:1,E={imports:I,parse:function(d,e,f){var g,h,i,j,k,l=null,m="";if(y=z=D=A=0,j=f&&f.globalVars?w.Parser.serializeVars(f.globalVars)+"\n":"",k=f&&f.modifyVars?"\n"+w.Parser.serializeVars(f.modifyVars):"",(j||f&&f.banner)&&(m=(f&&f.banner?f.banner:"")+j,E.imports.contentsIgnoredChars[a.currentFileInfo.filename]=m.length),d=d.replace(/\r\n/g,"\n"),v=d=m+d.replace(/^\uFEFF/,"")+k,E.imports.contents[a.currentFileInfo.filename]=d,B=function(b){function c(b,c){l=new u({index:c||i,type:"Parse",message:b,filename:a.currentFileInfo.filename},a)}function d(a){var c=i-s;512>c&&!a||!c||(r.push(b.slice(s,i+1)),s=i+1)}var e,f,g,h,i,j,k,m,n,o=b.length,p=0,q=0,r=[],s=0;for(i=0;o>i;i++)if(k=b.charCodeAt(i),!(k>=97&&122>=k||34>k))switch(k){case 40:q++,f=i;continue;case 41:if(--q<0)return c("missing opening `(`");continue;case 59:q||d();continue;case 123:p++,e=i;continue;case 125:if(--p<0)return c("missing opening `{`");p||q||d();continue;case 92:if(o-1>i){i++;continue}return c("unescaped `\\`");case 34:case 39:case 96:for(n=0,j=i,i+=1;o>i;i++)if(m=b.charCodeAt(i),!(m>96)){if(m==k){n=1;break}if(92==m){if(i==o-1)return c("unescaped `\\`");i++}}if(n)continue;return c("unmatched `"+String.fromCharCode(k)+"`",j);case 47:if(q||i==o-1)continue;if(m=b.charCodeAt(i+1),47==m)for(i+=2;o>i&&(m=b.charCodeAt(i),!(13>=m)||10!=m&&13!=m);i++);else if(42==m){for(g=j=i,i+=2;o-1>i&&(m=b.charCodeAt(i),125==m&&(h=i),42!=m||47!=b.charCodeAt(i+1));i++);if(i==o-1)return c("missing closing `*/`",j);i++}continue;case 42:if(o-1>i&&47==b.charCodeAt(i+1))return c("unmatched `/*`");continue}return 0!==p?g>e&&h>g?c("missing closing `}` or `*/`",e):c("missing closing `}`",e):0!==q?c("missing closing `)`",f):(d(!0),r)}(d),l)return e(new u(l,a));C=B[0];try{g=new x.Ruleset(null,this.parsers.primary()),g.root=!0,g.firstRoot=!0}catch(n){return e(new u(n,a))}if(g.toCSS=function(d){return function(e,f){e=e||{};var g,h,i=new x.evalEnv(e);"object"!=typeof f||Array.isArray(f)||(f=Object.keys(f).map(function(a){var b=f[a];return b instanceof x.Value||(b instanceof x.Expression||(b=new x.Expression([b])),b=new x.Value([b])),new x.Rule("@"+a,b,!1,null,0)}),i.frames=[new x.Ruleset(null,f)]);try{var j,k=[],l=[new x.joinSelectorVisitor,new x.processExtendsVisitor,new x.toCSSVisitor({compress:Boolean(e.compress)})],m=this;if(e.plugins)for(j=0;j57||43>b||47===b||44==b))return a=j(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/),a?new x.Dimension(a[1],a[2]):void 0},unicodeDescriptor:function(){var a;return a=j(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/),a?new x.UnicodeDescriptor(a[0]):void 0},javascript:function(){var c,d,e=y;return"~"===v.charAt(e)&&(e++,d=!0),"`"===v.charAt(e)?(a.javascriptEnabled===b||a.javascriptEnabled||o("You are using JavaScript, which has been disabled."),d&&k("~"),c=j(/^`([^`]*)`/),c?new x.JavaScript(c[1],y,d):void 0):void 0}},variable:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*:/))?a[1]:void 0},rulesetCall:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*\(\s*\)\s*;/))?new x.RulesetCall(a[1]):void 0},extend:function(a){var b,c,d,e,f,g=y;if(j(a?/^&:extend\(/:/^:extend\(/)){do{for(d=null,b=null;!(d=j(/^(all)(?=\s*(\)|,))/))&&(c=this.element());)b?b.push(c):b=[c];d=d&&d[1],f=new x.Extend(new x.Selector(b),d,g),e?e.push(f):e=[f]}while(k(","));return m(/^\)/),a&&m(/^;/),e}},extendRule:function(){return this.extend(!0)},mixin:{call:function(){var b,c,g,h,i,l,m=v.charAt(y),o=!1,p=y;if("."===m||"#"===m){for(d();;){if(b=y,h=j(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/),!h)break;g=new x.Element(i,h,b,a.currentFileInfo),c?c.push(g):c=[g],i=k(">")}return c&&(k("(")&&(l=this.args(!0).args,n(")")),F.important()&&(o=!0),F.end())?(f(),new x.mixin.Call(c,l,p,a.currentFileInfo,o)):void e()}},args:function(a){var b,c,g,h,i,l,m=E.parsers,n=m.entities,p={args:null,variadic:!1},q=[],r=[],s=[];for(d();;){if(a)l=m.detachedRuleset()||m.expression();else{if(m.comments(),"."===v.charAt(y)&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({variadic:!0});break}l=n.variable()||n.literal()||n.keyword()}if(!l)break;h=null,l.throwAwayComments&&l.throwAwayComments(),i=l;var t=null;if(a?l.value&&1==l.value.length&&(t=l.value[0]):t=l,t&&t instanceof x.Variable)if(k(":")){if(q.length>0&&(b&&o("Cannot mix ; and , as delimiter types"),c=!0),i=a&&m.detachedRuleset()||m.expression(),!i){if(!a)return e(),p.args=[],p;o("could not understand value for named argument")}h=g=t.name}else{if(!a&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({name:l.name,variadic:!0});break}a||(g=h=t.name,i=null)}i&&q.push(i),s.push({name:h,value:i}),k(",")||(k(";")||b)&&(c&&o("Cannot mix ; and , as delimiter types"),b=!0,q.length>1&&(i=new x.Value(q)),r.push({name:g,value:i}),g=null,q=[],c=!1)}return f(),p.args=b?r:s,p},definition:function(){var a,b,c,g,h=[],i=!1;if(!("."!==v.charAt(y)&&"#"!==v.charAt(y)||p(/^[^{]*\}/)))if(d(),b=j(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){a=b[1];var l=this.args(!1);if(h=l.args,i=l.variadic,!k(")"))return A=y,void e();if(F.comments(),j(/^when/)&&(g=m(F.conditions,"expected condition")),c=F.block())return f(),new x.mixin.Definition(a,h,c,g,i);e()}else f()}},entity:function(){var a=this.entities;return a.literal()||a.variable()||a.url()||a.call()||a.keyword()||a.javascript()||this.comment()},end:function(){return k(";")||q("}")},alpha:function(){var a;if(j(/^\(opacity=/i))return a=j(/^\d+/)||this.entities.variable(),a?(n(")"),new x.Alpha(a)):void 0},element:function(){var b,c,g,h=y;return c=this.combinator(),b=j(/^(?:\d+\.\d+|\d+)%/)||j(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||k("*")||k("&")||this.attribute()||j(/^\([^()@]+\)/)||j(/^[\.#](?=@)/)||this.entities.variableCurly(),b||(d(),k("(")?(g=this.selector())&&k(")")?(b=new x.Paren(g),f()):e():f()),b?new x.Element(c,b,h,a.currentFileInfo):void 0},combinator:function(){var a=v.charAt(y);if(">"===a||"+"===a||"~"===a||"|"===a||"^"===a){for(y++,"^"===v.charAt(y)&&(a="^^",y++);h(v,y);)y++;return new x.Combinator(a)}return new x.Combinator(h(v,y-1)?" ":null)},lessSelector:function(){return this.selector(!0)},selector:function(b){for(var c,d,e,f,g,h,i,j=y,k=J;(b&&(g=this.extend())||b&&(h=k(/^when/))||(f=this.element()))&&(h?i=m(this.conditions,"expected condition"):i?o("CSS guard can only be used at the end of selector"):g?d?d.push(g):d=[g]:(d&&o("Extend can only be used at the end of selector"),e=v.charAt(y),c?c.push(f):c=[f],f=null),"{"!==e&&"}"!==e&&";"!==e&&","!==e&&")"!==e););return c?new x.Selector(c,d,i,j,a.currentFileInfo):void(d&&o("Extend must be used to extend a selector, it cannot be used on its own"))},attribute:function(){if(k("[")){var a,b,c,d=this.entities;return(a=d.variableCurly())||(a=m(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/)),c=j(/^[|~*$^]?=/),c&&(b=d.quoted()||j(/^[0-9]+%/)||j(/^[\w-]+/)||d.variableCurly()),n("]"),new x.Attribute(a,c,b)}},block:function(){var a;return k("{")&&(a=this.primary())&&k("}")?a:void 0},blockRuleset:function(){var a=this.block();return a&&(a=new x.Ruleset(null,a)),a},detachedRuleset:function(){var a=this.blockRuleset();return a?new x.DetachedRuleset(a):void 0},ruleset:function(){var b,c,g,h;for(d(),a.dumpLineNumbers&&(h=t(y,v,a));;){if(c=this.lessSelector(),!c)break;if(b?b.push(c):b=[c],this.comments(),c.condition&&b.length>1&&o("Guards are only currently allowed on a single selector."),!k(","))break;c.condition&&o("Guards are only currently allowed on a single selector."),this.comments()}if(b&&(g=this.block())){f();var i=new x.Ruleset(b,g,a.strictImports);return a.dumpLineNumbers&&(i.debugInfo=h),i}A=y,e()},rule:function(b){var c,g,h,i,j,k=y,l=v.charAt(k);if("."!==l&&"#"!==l&&"&"!==l)if(d(),c=this.variable()||this.ruleProperty()){if(j="string"==typeof c,j&&(g=this.detachedRuleset()),g||(g=b||!a.compress&&!j?this.anonymousValue()||this.value():this.value()||this.anonymousValue(),h=this.important(),i=!j&&c.pop().value),g&&this.end())return f(),new x.Rule(c,g,h,i,k,a.currentFileInfo);if(A=y,e(),g&&!b)return this.rule(!0)}else f()},anonymousValue:function(){var a;return a=/^([^@+\/'"*`(;{}-]*);/.exec(C),a?(y+=a[0].length-1,new x.Anonymous(a[1])):void 0},"import":function(){var b,c,g=y;d();var h=j(/^@import?\s+/),i=(h?this.importOptions():null)||{};return h&&(b=this.entities.quoted()||this.entities.url())&&(c=this.mediaFeatures(),k(";"))?(f(),c=c&&new x.Value(c),new x.Import(b,c,i,g,a.currentFileInfo)):void e()},importOptions:function(){var a,b,c,d={};if(!k("("))return null;do if(a=this.importOption()){switch(b=a,c=!0,b){case"css":b="less",c=!1;break;case"once":b="multiple",c=!1}if(d[b]=c,!k(","))break}while(a);return n(")"),d},importOption:function(){var a=j(/^(less|css|multiple|once|inline|reference)/);return a?a[1]:void 0},mediaFeature:function(){var b,c,d=this.entities,e=[];do if(b=d.keyword()||d.variable())e.push(b);else if(k("(")){if(c=this.property(),b=this.value(),!k(")"))return null;if(c&&b)e.push(new x.Paren(new x.Rule(c,b,null,null,y,a.currentFileInfo,!0)));else{if(!b)return null;e.push(new x.Paren(b))}}while(b);return e.length>0?new x.Expression(e):void 0},mediaFeatures:function(){var a,b=this.entities,c=[];do if(a=this.mediaFeature()){if(c.push(a),!k(","))break}else if(a=b.variable(),a&&(c.push(a),!k(",")))break;while(a);return c.length>0?c:null},media:function(){var b,c,d,e;return a.dumpLineNumbers&&(e=t(y,v,a)),j(/^@media/)&&(b=this.mediaFeatures(),c=this.block())?(d=new x.Media(c,b,y,a.currentFileInfo),a.dumpLineNumbers&&(d.debugInfo=e),d):void 0},directive:function(){var b,c,g,h,i,l,m,n=y,p=!0;if("@"===v.charAt(y)){if(c=this["import"]()||this.media())return c;if(d(),b=j(/^@[a-z-]+/)){switch(h=b,"-"==b.charAt(1)&&b.indexOf("-",2)>0&&(h="@"+b.slice(b.indexOf("-",2)+1)),h){case"@charset":i=!0,p=!1;break;case"@namespace":l=!0,p=!1;break;case"@keyframes":i=!0;break;case"@host":case"@page":case"@document":case"@supports":m=!0}return i?(c=this.entity(),c||o("expected "+b+" identifier")):l?(c=this.expression(),c||o("expected "+b+" expression")):m&&(c=(j(/^[^{;]+/)||"").trim(),c&&(c=new x.Anonymous(c))),p&&(g=this.blockRuleset()),g||!p&&c&&k(";")?(f(),new x.Directive(b,c,g,n,a.currentFileInfo,a.dumpLineNumbers?t(n,v,a):null)):void e()}}},value:function(){var a,b=[];do if(a=this.expression(),a&&(b.push(a),!k(",")))break;while(a);return b.length>0?new x.Value(b):void 0},important:function(){return"!"===v.charAt(y)?j(/^! *important/):void 0},sub:function(){var a,b;return k("(")&&(a=this.addition())?(b=new x.Expression([a]),n(")"),b.parens=!0,b):void 0},multiplication:function(){var a,b,c,d,e;if(a=this.operand()){for(e=h(v,y-1);;){if(p(/^\/[*\/]/))break;if(c=k("/")||k("*"),!c)break;if(b=this.operand(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new x.Operation(c,[d||a,b],e),e=h(v,y-1)}return d||a}},addition:function(){var a,b,c,d,e;if(a=this.multiplication()){for(e=h(v,y-1);;){if(c=j(/^[-+]\s+/)||!e&&(k("+")||k("-")),!c)break;if(b=this.multiplication(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new x.Operation(c,[d||a,b],e),e=h(v,y-1)}return d||a}},conditions:function(){var a,b,c,d=y;if(a=this.condition()){for(;;){if(!p(/^,\s*(not\s*)?\(/)||!k(","))break;if(b=this.condition(),!b)break;c=new x.Condition("or",c||a,b,d)}return c||a}},condition:function(){var a,b,c,d,e=this.entities,f=y,g=!1;return j(/^not/)&&(g=!0),n("("),a=this.addition()||e.keyword()||e.quoted(),a?(d=j(/^(?:>=|<=|=<|[<=>])/),d?(b=this.addition()||e.keyword()||e.quoted(),b?c=new x.Condition(d,a,b,f,g):o("expected expression")):c=new x.Condition("=",a,new x.Keyword("true"),f,g),n(")"),j(/^and/)?new x.Condition("and",c,this.condition()):c):void 0},operand:function(){var a,b=this.entities,c=v.charAt(y+1);"-"!==v.charAt(y)||"@"!==c&&"("!==c||(a=k("-"));var d=this.sub()||b.dimension()||b.color()||b.variable()||b.call();return a&&(d.parensInOp=!0,d=new x.Negative(d)),d},expression:function(){var a,b,c=[];do a=this.addition()||this.entity(),a&&(c.push(a),p(/^\/[\/*]/)||(b=k("/"),b&&c.push(new x.Anonymous(b))));while(a);return c.length>0?new x.Expression(c):void 0},property:function(){var a=j(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);return a?a[1]:void 0},ruleProperty:function(){function b(a){var b=a.exec(e);return b?(g.push(y+h),h+=b[0].length,e=e.slice(b[1].length),f.push(b[1])):void 0}var c,d,e=C,f=[],g=[],h=0;for(b(/^(\*?)/);b(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/););if(f.length>1&&b(/^\s*((?:\+_|\+)?)\s*:/)){for(l(h),""===f[0]&&(f.shift(),g.shift()),d=0;dl;l++)e=b.rgb[l]/255,f=c.rgb[l]/255,h=a(e,f),g&&(h=(j*f+i*(e-j*(e+f-h)))/g),k[l]=255*h;return new d.Color(k,g)}function g(){var a,b=d.functions;for(a in l)l.hasOwnProperty(a)&&(b[a]=e.bind(null,Math[a],l[a]));for(a in m)m.hasOwnProperty(a)&&(b[a]=f.bind(null,m[a]));a=d.defaultFunc,b["default"]=a.eval.bind(a)}function h(a){return d.functions.hsla(a.h,a.s,a.l,a.a)}function i(a,b){return a instanceof d.Dimension&&a.unit.is("%")?parseFloat(a.value*b/100):j(a)}function j(a){if(a instanceof d.Dimension)return parseFloat(a.unit.is("%")?a.value/100:a.value);if("number"==typeof a)return a;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function k(a){return Math.min(1,Math.max(0,a))}d.functions={rgb:function(a,b,c){return this.rgba(a,b,c,1)},rgba:function(a,b,c,e){var f=[a,b,c].map(function(a){return i(a,255)});return e=j(e),new d.Color(f,e)},hsl:function(a,b,c){return this.hsla(a,b,c,1)},hsla:function(a,b,c,d){function e(a){return a=0>a?a+1:a>1?a-1:a,1>6*a?g+(f-g)*a*6:1>2*a?f:2>3*a?g+(f-g)*(2/3-a)*6:g}a=j(a)%360/360,b=k(j(b)),c=k(j(c)),d=k(j(d));var f=.5>=c?c*(b+1):c+b-c*b,g=2*c-f;return this.rgba(255*e(a+1/3),255*e(a),255*e(a-1/3),d)},hsv:function(a,b,c){return this.hsva(a,b,c,1)},hsva:function(a,b,c,d){a=j(a)%360/360*360,b=j(b),c=j(c),d=j(d);var e,f;e=Math.floor(a/60%6),f=a/60-e;var g=[c,c*(1-b),c*(1-f*b),c*(1-(1-f)*b)],h=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(255*g[h[e][0]],255*g[h[e][1]],255*g[h[e][2]],d)},hue:function(a){return new d.Dimension(Math.round(a.toHSL().h))},saturation:function(a){return new d.Dimension(Math.round(100*a.toHSL().s),"%")},lightness:function(a){return new d.Dimension(Math.round(100*a.toHSL().l),"%")},hsvhue:function(a){return new d.Dimension(Math.round(a.toHSV().h))},hsvsaturation:function(a){return new d.Dimension(Math.round(100*a.toHSV().s),"%")},hsvvalue:function(a){return new d.Dimension(Math.round(100*a.toHSV().v),"%")},red:function(a){return new d.Dimension(a.rgb[0])},green:function(a){return new d.Dimension(a.rgb[1])},blue:function(a){return new d.Dimension(a.rgb[2])},alpha:function(a){return new d.Dimension(a.toHSL().a)},luma:function(a){return new d.Dimension(Math.round(a.luma()*a.alpha*100),"%")},luminance:function(a){var b=.2126*a.rgb[0]/255+.7152*a.rgb[1]/255+.0722*a.rgb[2]/255;return new d.Dimension(Math.round(b*a.alpha*100),"%")},saturate:function(a,b){if(!a.rgb)return null;var c=a.toHSL();return c.s+=b.value/100,c.s=k(c.s),h(c)},desaturate:function(a,b){var c=a.toHSL();return c.s-=b.value/100,c.s=k(c.s),h(c)},lighten:function(a,b){var c=a.toHSL();return c.l+=b.value/100,c.l=k(c.l),h(c)},darken:function(a,b){var c=a.toHSL();return c.l-=b.value/100,c.l=k(c.l),h(c)},fadein:function(a,b){var c=a.toHSL();return c.a+=b.value/100,c.a=k(c.a),h(c)},fadeout:function(a,b){var c=a.toHSL();return c.a-=b.value/100,c.a=k(c.a),h(c)},fade:function(a,b){var c=a.toHSL();return c.a=b.value/100,c.a=k(c.a),h(c)},spin:function(a,b){var c=a.toHSL(),d=(c.h+b.value)%360;return c.h=0>d?360+d:d,h(c)},mix:function(a,b,c){c||(c=new d.Dimension(50));var e=c.value/100,f=2*e-1,g=a.toHSL().a-b.toHSL().a,h=((f*g==-1?f:(f+g)/(1+f*g))+1)/2,i=1-h,j=[a.rgb[0]*h+b.rgb[0]*i,a.rgb[1]*h+b.rgb[1]*i,a.rgb[2]*h+b.rgb[2]*i],k=a.alpha*e+b.alpha*(1-e);return new d.Color(j,k)},greyscale:function(a){return this.desaturate(a,new d.Dimension(100))},contrast:function(a,b,c,d){if(!a.rgb)return null;if("undefined"==typeof c&&(c=this.rgba(255,255,255,1)),"undefined"==typeof b&&(b=this.rgba(0,0,0,1)),b.luma()>c.luma()){var e=c;c=b,b=e}return d="undefined"==typeof d?.43:j(d),a.luma()i.value)&&(m[f]=g);else{if(k!==b&&j!==k)throw{type:"Argument",message:"incompatible types"};n[j]=m.length,m.push(g)}else Array.isArray(c[e].value)&&Array.prototype.push.apply(c,Array.prototype.slice.call(c[e].value));return 1==m.length?m[0]:(c=m.map(function(a){return a.toCSS(this.env)}).join(this.env.compress?",":", "),new d.Anonymous((a?"min":"max")+"("+c+")"))},min:function(){return this._minmax(!0,arguments)},max:function(){return this._minmax(!1,arguments)},"get-unit":function(a){return new d.Anonymous(a.unit)},argb:function(a){return new d.Anonymous(a.toARGB())},percentage:function(a){return new d.Dimension(100*a.value,"%")},color:function(a){if(a instanceof d.Quoted){var b,c=a.value;if(b=d.Color.fromKeyword(c))return b;if(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(c))return new d.Color(c.slice(1));throw{type:"Argument",message:"argument must be a color keyword or 3/6 digit hex e.g. #FFF"}}throw{type:"Argument",message:"argument must be a string"}},iscolor:function(a){return this._isa(a,d.Color)},isnumber:function(a){return this._isa(a,d.Dimension)},isstring:function(a){return this._isa(a,d.Quoted)},iskeyword:function(a){return this._isa(a,d.Keyword)},isurl:function(a){return this._isa(a,d.URL)},ispixel:function(a){return this.isunit(a,"px")},ispercentage:function(a){return this.isunit(a,"%")},isem:function(a){return this.isunit(a,"em")},isunit:function(a,b){return a instanceof d.Dimension&&a.unit.is(b.value||b)?d.True:d.False},_isa:function(a,b){return a instanceof b?d.True:d.False},tint:function(a,b){return this.mix(this.rgb(255,255,255),a,b)},shade:function(a,b){return this.mix(this.rgb(0,0,0),a,b)},extract:function(a,b){return b=b.value-1,Array.isArray(a.value)?a.value[b]:Array(a)[b]},length:function(a){var b=Array.isArray(a.value)?a.value.length:1;return new d.Dimension(b)},"data-uri":function(b,e){if("undefined"!=typeof a)return new d.URL(e||b,this.currentFileInfo).eval(this.env);var f=b.value,g=e&&e.value,h=c("fs"),i=c("path"),j=!1;if(arguments.length<2&&(g=f),this.env.isPathRelative(g)&&(g=this.currentFileInfo.relativeUrls?i.join(this.currentFileInfo.currentDirectory,g):i.join(this.currentFileInfo.entryPath,g)),arguments.length<2){var k;try{k=c("mime")}catch(l){k=d._mime}f=k.lookup(g);var m=k.charsets.lookup(f);j=["US-ASCII","UTF-8"].indexOf(m)<0,j&&(f+=";base64")}else j=/;base64$/.test(f);var n=h.readFileSync(g),o=32,p=parseInt(n.length/1024,10);if(p>=o&&this.env.ieCompat!==!1)return this.env.silent||console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!",g,p,o),new d.URL(e||b,this.currentFileInfo).eval(this.env);n=j?n.toString("base64"):encodeURIComponent(n);var q='"data:'+f+","+n+'"';return new d.URL(new d.Anonymous(q))},"svg-gradient":function(a){function e(){throw{type:"Argument",message:"svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]"}}arguments.length<3&&e();var f,g,h,i,j,k,l,m=Array.prototype.slice.call(arguments,1),n="linear",o='x="0" y="0" width="1" height="1"',p=!0,q={compress:!1},r=a.toCSS(q);switch(r){case"to bottom":f='x1="0%" y1="0%" x2="0%" y2="100%"';break;case"to right":f='x1="0%" y1="0%" x2="100%" y2="0%"';break;case"to bottom right":f='x1="0%" y1="0%" x2="100%" y2="100%"';break;case"to top right":f='x1="0%" y1="100%" x2="100%" y2="0%"';break;case"ellipse":case"ellipse at center":n="radial",f='cx="50%" cy="50%" r="75%"',o='x="-50" y="-50" width="101" height="101"';break;default:throw{type:"Argument",message:"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"}}for(g='<'+n+'Gradient id="gradient" gradientUnits="userSpaceOnUse" '+f+">",h=0;hl?' stop-opacity="'+l+'"':"")+"/>";if(g+="',p)try{g=c("./encoder").encodeBase64(g)}catch(s){p=!1}return g="'data:image/svg+xml"+(p?";base64":"")+","+g+"'",new d.URL(new d.Anonymous(g))}},d._mime={_types:{".htm":"text/html",".html":"text/html",".gif":"image/gif",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png"},lookup:function(a){var e=c("path").extname(a),f=d._mime._types[e];if(f===b)throw new Error('Optional dependency "mime" is required for '+e);return f},charsets:{lookup:function(a){return a&&/^text\//.test(a)?"UTF-8":""}}};var l={ceil:null,floor:null,sqrt:null,abs:null,tan:"",sin:"",cos:"",atan:"rad",asin:"rad",acos:"rad"},m={multiply:function(a,b){return a*b},screen:function(a,b){return a+b-a*b},overlay:function(a,b){return a*=2,1>=a?m.multiply(a,b):m.screen(a-1,b)},softlight:function(a,b){var c=1,d=a;return b>.5&&(d=1,c=a>.25?Math.sqrt(a):((16*a-12)*a+4)*a),a-(1-2*b)*d*(c-a)},hardlight:function(a,b){return m.overlay(b,a)},difference:function(a,b){return Math.abs(a-b)},exclusion:function(a,b){return a+b-2*a*b},average:function(a,b){return(a+b)/2},negation:function(a,b){return 1-Math.abs(a+b-1)}};d.defaultFunc={eval:function(){var a=this.value_,b=this.error_;if(b)throw b;return null!=a?a?d.True:d.False:void 0},value:function(a){this.value_=a},error:function(a){this.error_=a},reset:function(){this.value_=this.error_=null}},g(),d.fround=function(a,b){var c;return a&&null!=a.numPrecision?(c=Math.pow(10,a.numPrecision),Math.round(b*c)/c):b},d.functionCall=function(a,b){this.env=a,this.currentFileInfo=b},d.functionCall.prototype=d.functions}(c("./tree")),function(a){a.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"}}(c("./tree")),function(a){a.debugInfo=function(b,c,d){var e="";if(b.dumpLineNumbers&&!b.compress)switch(b.dumpLineNumbers){case"comments":e=a.debugInfo.asComment(c);break;case"mediaquery":e=a.debugInfo.asMediaQuery(c);break;case"all":e=a.debugInfo.asComment(c)+(d||"")+a.debugInfo.asMediaQuery(c)}return e},a.debugInfo.asComment=function(a){return"/* line "+a.debugInfo.lineNumber+", "+a.debugInfo.fileName+" */\n"},a.debugInfo.asMediaQuery=function(a){return"@media -sass-debug-info{filename{font-family:"+("file://"+a.debugInfo.fileName).replace(/([.:\/\\])/g,function(a){return"\\"==a&&(a="/"),"\\"+a})+"}line{font-family:\\00003"+a.debugInfo.lineNumber+"}}\n"},a.find=function(a,b){for(var c,d=0;d1?"["+a.value.map(function(a){return a.toCSS(!1)}).join(", ")+"]":a.toCSS(!1)},a.toCSS=function(a){var b=[];return this.genCSS(a,{add:function(a){b.push(a)},isEmpty:function(){return 0===b.length}}),b.join("")},a.outputRuleset=function(a,b,c){var d,e=c.length;if(a.tabLevel=(0|a.tabLevel)+1,a.compress){for(b.add("{"),d=0;e>d;d++)c[d].genCSS(a,b);return b.add("}"),void a.tabLevel--}var f="\n"+Array(a.tabLevel).join(" "),g=f+" ";if(e){for(b.add(" {"+g),c[0].genCSS(a,b),d=1;e>d;d++)b.add(g),c[d].genCSS(a,b);b.add(f+"}")}else b.add(" {"+f+"}");a.tabLevel--}}(c("./tree")),function(a){a.Alpha=function(a){this.value=a},a.Alpha.prototype={type:"Alpha",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Alpha(this.value.eval(b)):this},genCSS:function(a,b){b.add("alpha(opacity="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value),b.add(")")},toCSS:a.toCSS}}(c("../tree")),function(a){a.Anonymous=function(a,b,c,d){this.value=a.value||a,this.index=b,this.mapLines=d,this.currentFileInfo=c},a.Anonymous.prototype={type:"Anonymous",eval:function(){return new a.Anonymous(this.value,this.index,this.currentFileInfo,this.mapLines)},compare:function(a){if(!a.toCSS)return-1;var b=this.toCSS(),c=a.toCSS();return b===c?0:c>b?-1:1},genCSS:function(a,b){b.add(this.value,this.currentFileInfo,this.index,this.mapLines)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Assignment=function(a,b){this.key=a,this.value=b},a.Assignment.prototype={type:"Assignment",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Assignment(this.key,this.value.eval(b)):this},genCSS:function(a,b){b.add(this.key+"="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Call=function(a,b,c,d){this.name=a,this.args=b,this.index=c,this.currentFileInfo=d},a.Call.prototype={type:"Call",accept:function(a){this.args&&(this.args=a.visitArray(this.args))},eval:function(b){var c,d,e=this.args.map(function(a){return a.eval(b)}),f=this.name.toLowerCase();if(f in a.functions)try{if(d=new a.functionCall(b,this.currentFileInfo),c=d[f].apply(d,e),null!=c)return c}catch(g){throw{type:g.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(g.message?": "+g.message:""),index:this.index,filename:this.currentFileInfo.filename}}return new a.Call(this.name,e,this.index,this.currentFileInfo)},genCSS:function(a,b){b.add(this.name+"(",this.currentFileInfo,this.index);for(var c=0;ca?"0":"")+a.toString(16)}).join("")}function c(a,b){return Math.min(Math.max(a,0),b)}a.Color=function(a,b){this.rgb=Array.isArray(a)?a:6==a.length?a.match(/.{2}/g).map(function(a){return parseInt(a,16)}):a.split("").map(function(a){return parseInt(a+a,16)}),this.alpha="number"==typeof b?b:1};var d="transparent";a.Color.prototype={type:"Color",eval:function(){return this},luma:function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255;return a=.03928>=a?a/12.92:Math.pow((a+.055)/1.055,2.4),b=.03928>=b?b/12.92:Math.pow((b+.055)/1.055,2.4),c=.03928>=c?c/12.92:Math.pow((c+.055)/1.055,2.4),.2126*a+.7152*b+.0722*c},genCSS:function(a,b){b.add(this.toCSS(a))},toCSS:function(b,e){var f=b&&b.compress&&!e,g=a.fround(b,this.alpha);if(1>g)return 0===g&&this.isTransparentKeyword?d:"rgba("+this.rgb.map(function(a){return c(Math.round(a),255)}).concat(c(g,1)).join(","+(f?"":" "))+")";var h=this.toRGB();if(f){var i=h.split("");i[1]===i[2]&&i[3]===i[4]&&i[5]===i[6]&&(h="#"+i[1]+i[3]+i[5])}return h},operate:function(b,c,d){for(var e=[],f=this.alpha*(1-d.alpha)+d.alpha,g=0;3>g;g++)e[g]=a.operate(b,c,this.rgb[g],d.rgb[g]);return new a.Color(e,f)},toRGB:function(){return b(this.rgb)},toHSL:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=(g+h)/2,j=g-h;if(g===h)a=b=0;else{switch(b=i>.5?j/(2-g-h):j/(g+h),g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,l:i,a:f}},toHSV:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=g,j=g-h;if(b=0===g?0:j/g,g===h)a=0;else{switch(g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,v:i,a:f}},toARGB:function(){return b([255*this.alpha].concat(this.rgb))},compare:function(a){return a.rgb?a.rgb[0]===this.rgb[0]&&a.rgb[1]===this.rgb[1]&&a.rgb[2]===this.rgb[2]&&a.alpha===this.alpha?0:-1:-1}},a.Color.fromKeyword=function(b){if(b=b.toLowerCase(),a.colors.hasOwnProperty(b))return new a.Color(a.colors[b].slice(1));if(b===d){var c=new a.Color([0,0,0],0);return c.isTransparentKeyword=!0,c}}}(c("../tree")),function(a){a.Comment=function(a,b,c,d){this.value=a,this.silent=!!b,this.currentFileInfo=d},a.Comment.prototype={type:"Comment",genCSS:function(b,c){this.debugInfo&&c.add(a.debugInfo(b,this),this.currentFileInfo,this.index),c.add(this.value.trim())},toCSS:a.toCSS,isSilent:function(a){var b=this.currentFileInfo&&this.currentFileInfo.reference&&!this.isReferenced,c=a.compress&&!this.value.match(/^\/\*!/);return this.silent||b||c},eval:function(){return this},markReferenced:function(){this.isReferenced=!0}}}(c("../tree")),function(a){a.Condition=function(a,b,c,d,e){this.op=a.trim(),this.lvalue=b,this.rvalue=c,this.index=d,this.negate=e},a.Condition.prototype={type:"Condition",accept:function(a){this.lvalue=a.visit(this.lvalue),this.rvalue=a.visit(this.rvalue)},eval:function(a){var b,c=this.lvalue.eval(a),d=this.rvalue.eval(a),e=this.index;return b=function(a){switch(a){case"and":return c&&d;case"or":return c||d;default:if(c.compare)b=c.compare(d);else{if(!d.compare)throw{type:"Type",message:"Unable to perform comparison",index:e};b=d.compare(c)}switch(b){case-1:return"<"===a||"=<"===a||"<="===a;case 0:return"="===a||">="===a||"=<"===a||"<="===a;case 1:return">"===a||">="===a}}}(this.op),this.negate?!b:b}}}(c("../tree")),function(a){a.DetachedRuleset=function(a,b){this.ruleset=a,this.frames=b},a.DetachedRuleset.prototype={type:"DetachedRuleset",accept:function(a){this.ruleset=a.visit(this.ruleset)},eval:function(b){var c=this.frames||b.frames.slice(0);return new a.DetachedRuleset(this.ruleset,c)},callEval:function(b){return this.ruleset.eval(this.frames?new a.evalEnv(b,this.frames.concat(b.frames)):b)}}}(c("../tree")),function(a){a.Dimension=function(c,d){this.value=parseFloat(c),this.unit=d&&d instanceof a.Unit?d:new a.Unit(d?[d]:b)},a.Dimension.prototype={type:"Dimension",accept:function(a){this.unit=a.visit(this.unit)},eval:function(){return this},toColor:function(){return new a.Color([this.value,this.value,this.value])},genCSS:function(b,c){if(b&&b.strictUnits&&!this.unit.isSingular())throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());var d=a.fround(b,this.value),e=String(d);if(0!==d&&1e-6>d&&d>-1e-6&&(e=d.toFixed(20).replace(/0+$/,"")),b&&b.compress){if(0===d&&this.unit.isLength())return void c.add(e);d>0&&1>d&&(e=e.substr(1))}c.add(e),this.unit.genCSS(b,c)},toCSS:a.toCSS,operate:function(b,c,d){var e=a.operate(b,c,this.value,d.value),f=this.unit.clone();if("+"===c||"-"===c)if(0===f.numerator.length&&0===f.denominator.length)f.numerator=d.unit.numerator.slice(0),f.denominator=d.unit.denominator.slice(0);else if(0===d.unit.numerator.length&&0===f.denominator.length);else{if(d=d.convertTo(this.unit.usedUnits()),b.strictUnits&&d.unit.toString()!==f.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+f.toString()+"' and '"+d.unit.toString()+"'.");e=a.operate(b,c,this.value,d.value)}else"*"===c?(f.numerator=f.numerator.concat(d.unit.numerator).sort(),f.denominator=f.denominator.concat(d.unit.denominator).sort(),f.cancel()):"/"===c&&(f.numerator=f.numerator.concat(d.unit.denominator).sort(),f.denominator=f.denominator.concat(d.unit.numerator).sort(),f.cancel());return new a.Dimension(e,f)},compare:function(b){if(b instanceof a.Dimension){var c,d,e,f;if(this.unit.isEmpty()||b.unit.isEmpty())c=this,d=b;else if(c=this.unify(),d=b.unify(),0!==c.unit.compare(d.unit))return-1;return e=c.value,f=d.value,f>e?-1:e>f?1:0}return-1},unify:function(){return this.convertTo({length:"px",duration:"s",angle:"rad"})},convertTo:function(b){var c,d,e,f,g,h=this.value,i=this.unit.clone(),j={};if("string"==typeof b){for(c in a.UnitConversions)a.UnitConversions[c].hasOwnProperty(b)&&(j={},j[c]=b);b=j}g=function(a,b){return e.hasOwnProperty(a)?(b?h/=e[a]/e[f]:h*=e[a]/e[f],f):a};for(d in b)b.hasOwnProperty(d)&&(f=b[d],e=a.UnitConversions[d],i.map(g));return i.cancel(),new a.Dimension(h,i)}},a.UnitConversions={length:{m:1,cm:.01,mm:.001,"in":.0254,px:.0254/96,pt:.0254/72,pc:.0254/72*12},duration:{s:1,ms:.001},angle:{rad:1/(2*Math.PI),deg:1/360,grad:.0025,turn:1}},a.Unit=function(a,b,c){this.numerator=a?a.slice(0).sort():[],this.denominator=b?b.slice(0).sort():[],this.backupUnit=c},a.Unit.prototype={type:"Unit",clone:function(){return new a.Unit(this.numerator.slice(0),this.denominator.slice(0),this.backupUnit)},genCSS:function(a,b){this.numerator.length>=1?b.add(this.numerator[0]):this.denominator.length>=1?b.add(this.denominator[0]):a&&a.strictUnits||!this.backupUnit||b.add(this.backupUnit)},toCSS:a.toCSS,toString:function(){var a,b=this.numerator.join("*");for(a=0;a0)for(b=0;e>b;b++)this.numerator.push(a);else if(0>e)for(b=0;-e>b;b++)this.denominator.push(a)}0===this.numerator.length&&0===this.denominator.length&&c&&(this.backupUnit=c),this.numerator.sort(),this.denominator.sort()}}}(c("../tree")),function(a){a.Directive=function(a,b,c,d,e,f){this.name=a,this.value=b,c&&(this.rules=c,this.rules.allowImports=!0),this.index=d,this.currentFileInfo=e,this.debugInfo=f},a.Directive.prototype={type:"Directive",accept:function(a){var b=this.value,c=this.rules;c&&(c=a.visit(c)),b&&(b=a.visit(b))},genCSS:function(b,c){var d=this.value,e=this.rules;c.add(this.name,this.currentFileInfo,this.index),d&&(c.add(" "),d.genCSS(b,c)),e?a.outputRuleset(b,c,[e]):c.add(";")},toCSS:a.toCSS,eval:function(b){var c=this.value,d=this.rules;return c&&(c=c.eval(b)),d&&(d=d.eval(b),d.root=!0),new a.Directive(this.name,c,d,this.index,this.currentFileInfo,this.debugInfo)},variable:function(b){return this.rules?a.Ruleset.prototype.variable.call(this.rules,b):void 0},find:function(){return this.rules?a.Ruleset.prototype.find.apply(this.rules,arguments):void 0},rulesets:function(){return this.rules?a.Ruleset.prototype.rulesets.apply(this.rules):void 0},markReferenced:function(){var a,b;if(this.isReferenced=!0,this.rules)for(b=this.rules.rules,a=0;a":" > ","|":"|","^":" ^ ","^^":" ^^ "},_outputMapCompressed:{"":""," ":" ",":":" :","+":"+","~":"~",">":">","|":"|","^":"^","^^":"^^"},genCSS:function(a,b){b.add((a.compress?this._outputMapCompressed:this._outputMap)[this.value])},toCSS:a.toCSS}}(c("../tree")),function(a){a.Expression=function(a){this.value=a},a.Expression.prototype={type:"Expression",accept:function(a){this.value&&(this.value=a.visitArray(this.value))},eval:function(b){var c,d=this.parens&&!this.parensInOp,e=!1;return d&&b.inParenthesis(),this.value.length>1?c=new a.Expression(this.value.map(function(a){return a.eval(b)})):1===this.value.length?(this.value[0].parens&&!this.value[0].parensInOp&&(e=!0),c=this.value[0].eval(b)):c=this,d&&b.outOfParenthesis(),this.parens&&this.parensInOp&&!b.isMathOn()&&!e&&(c=new a.Paren(c)),c},genCSS:function(a,b){for(var c=0;c0&&c.length&&""===c[0].combinator.value&&(c[0].combinator.value=" "),d=d.concat(a[b].elements);this.selfSelectors=[{elements:d}]}}}(c("../tree")),function(a){a.Import=function(a,c,d,e,f){if(this.options=d,this.index=e,this.path=a,this.features=c,this.currentFileInfo=f,this.options.less!==b||this.options.inline)this.css=!this.options.less||this.options.inline;else{var g=this.getPath();g&&/css([\?;].*)?$/.test(g)&&(this.css=!0)}},a.Import.prototype={type:"Import",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.path=a.visit(this.path),!this.options.inline&&this.root&&(this.root=a.visit(this.root))},genCSS:function(a,b){this.css&&(b.add("@import ",this.currentFileInfo,this.index),this.path.genCSS(a,b),this.features&&(b.add(" "),this.features.genCSS(a,b)),b.add(";"))},toCSS:a.toCSS,getPath:function(){if(this.path instanceof a.Quoted){var c=this.path.value;return this.css!==b||/(\.[a-z]*$)|([\?;].*)$/.test(c)?c:c+".less"}return this.path instanceof a.URL?this.path.value.value:null},evalForImport:function(b){return new a.Import(this.path.eval(b),this.features,this.options,this.index,this.currentFileInfo)},evalPath:function(b){var c=this.path.eval(b),d=this.currentFileInfo&&this.currentFileInfo.rootpath;if(!(c instanceof a.URL)){if(d){var e=c.value;e&&b.isPathRelative(e)&&(c.value=d+e)}c.value=b.normalizePath(c.value)}return c},eval:function(b){var c,d=this.features&&this.features.eval(b);if(this.skip&&("function"==typeof this.skip&&(this.skip=this.skip()),this.skip))return[];if(this.options.inline){var e=new a.Anonymous(this.root,0,{filename:this.importedFilename},!0);return this.features?new a.Media([e],this.features.value):[e]}if(this.css){var f=new a.Import(this.evalPath(b),d,this.options,this.index);if(!f.css&&this.error)throw this.error;return f}return c=new a.Ruleset(null,this.root.rules.slice(0)),c.evalImports(b),this.features?new a.Media(c.rules,this.features.value):c.rules}}}(c("../tree")),function(a){a.JavaScript=function(a,b,c){this.escaped=c,this.expression=a,this.index=b},a.JavaScript.prototype={type:"JavaScript",eval:function(b){var c,d=this,e={},f=this.expression.replace(/@\{([\w-]+)\}/g,function(c,e){return a.jsify(new a.Variable("@"+e,d.index).eval(b))});try{f=new Function("return ("+f+")")}catch(g){throw{message:"JavaScript evaluation error: "+g.message+" from `"+f+"`",index:this.index}}var h=b.frames[0].variables();for(var i in h)h.hasOwnProperty(i)&&(e[i.slice(1)]={value:h[i].value,toJS:function(){return this.value.eval(b).toCSS()}});try{c=f.call(e)}catch(g){throw{message:"JavaScript evaluation error: '"+g.name+": "+g.message.replace(/["]/g,"'")+"'",index:this.index}}return"number"==typeof c?new a.Dimension(c):"string"==typeof c?new a.Quoted('"'+c+'"',c,this.escaped,this.index):new a.Anonymous(Array.isArray(c)?c.join(", "):c)}}}(c("../tree")),function(a){a.Keyword=function(a){this.value=a},a.Keyword.prototype={type:"Keyword",eval:function(){return this},genCSS:function(a,b){if("%"===this.value)throw{type:"Syntax",message:"Invalid % without number"};b.add(this.value)},toCSS:a.toCSS,compare:function(b){return b instanceof a.Keyword?b.value===this.value?0:1:-1}},a.True=new a.Keyword("true"),a.False=new a.Keyword("false")}(c("../tree")),function(a){a.Media=function(b,c,d,e){this.index=d,this.currentFileInfo=e;var f=this.emptySelectors();this.features=new a.Value(c),this.rules=[new a.Ruleset(f,b)],this.rules[0].allowImports=!0},a.Media.prototype={type:"Media",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.rules&&(this.rules=a.visitArray(this.rules))},genCSS:function(b,c){c.add("@media ",this.currentFileInfo,this.index),this.features.genCSS(b,c),a.outputRuleset(b,c,this.rules)},toCSS:a.toCSS,eval:function(b){b.mediaBlocks||(b.mediaBlocks=[],b.mediaPath=[]);var c=new a.Media(null,[],this.index,this.currentFileInfo);this.debugInfo&&(this.rules[0].debugInfo=this.debugInfo,c.debugInfo=this.debugInfo);var d=!1;b.strictMath||(d=!0,b.strictMath=!0);try{c.features=this.features.eval(b)}finally{d&&(b.strictMath=!1)}return b.mediaPath.push(c),b.mediaBlocks.push(c),b.frames.unshift(this.rules[0]),c.rules=[this.rules[0].eval(b)],b.frames.shift(),b.mediaPath.pop(),0===b.mediaPath.length?c.evalTop(b):c.evalNested(b)},variable:function(b){return a.Ruleset.prototype.variable.call(this.rules[0],b)},find:function(){return a.Ruleset.prototype.find.apply(this.rules[0],arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.rules[0])},emptySelectors:function(){var b=new a.Element("","&",this.index,this.currentFileInfo),c=[new a.Selector([b],null,null,this.index,this.currentFileInfo)];return c[0].mediaEmpty=!0,c},markReferenced:function(){var a,b=this.rules[0].rules;for(this.rules[0].markReferenced(),this.isReferenced=!0,a=0;a1){var d=this.emptySelectors();c=new a.Ruleset(d,b.mediaBlocks),c.multiMedia=!0}return delete b.mediaBlocks,delete b.mediaPath,c},evalNested:function(b){var c,d,e=b.mediaPath.concat([this]);for(c=0;c0;c--)b.splice(c,0,new a.Anonymous("and"));return new a.Expression(b)})),new a.Ruleset([],[])},permute:function(a){if(0===a.length)return[];if(1===a.length)return a[0];for(var b=[],c=this.permute(a.slice(1)),d=0;d0){for(j=!0,g=0;gh;h++)s.value(h),r[h]=d.matchCondition(e,b);(r[0]||r[1])&&(r[0]!=r[1]&&(l.group=r[1]?u:v),q.push(l))}else q.push(l);p=!0}}for(s.reset(),n=[0,0,0],g=0;g0)m=v;else if(m=u,n[u]+n[v]>1)throw{type:"Runtime",message:"Ambiguous use of `default()` found when matching for `"+this.format(e)+"`",index:this.index,filename:this.currentFileInfo.filename};for(g=0;gh;h++)if(g=d[h],k=g&&g.name){for(l=!1,i=0;ii;i++)f.push(d[i].value.eval(b));n.prependRule(new a.Rule(k,new a.Expression(f).eval(b)))}else{if(j=g&&g.value)j=j.eval(b);else{if(!o[h].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+p+" for "+this.arity+")"};j=o[h].value.eval(c),n.resetCache()}n.prependRule(new a.Rule(k,j)),e[h]=j}if(o[h].variadic&&d)for(i=m;p>i;i++)e[i]=d[i].value.eval(b);m++}return n},eval:function(b){return new a.mixin.Definition(this.name,this.params,this.rules,this.condition,this.variadic,this.frames||b.frames.slice(0))},evalCall:function(b,c,d){var e,f,g=[],h=this.frames?this.frames.concat(b.frames):b.frames,i=this.evalParams(b,new a.evalEnv(b,h),c,g);return i.prependRule(new a.Rule("@arguments",new a.Expression(g).eval(b))),e=this.rules.slice(0),f=new a.Ruleset(null,e),f.originalRuleset=this,f=f.eval(new a.evalEnv(b,[this,i].concat(h))),d&&(f=this.parent.makeImportant.apply(f)),f},matchCondition:function(b,c){return this.condition&&!this.condition.eval(new a.evalEnv(c,[this.evalParams(c,new a.evalEnv(c,this.frames.concat(c.frames)),b,[])].concat(this.frames).concat(c.frames)))?!1:!0},matchArgs:function(a,b){var c,d=a&&a.length||0;if(this.variadic){if(dthis.params.length)return!1}c=Math.min(d,this.arity);for(var e=0;c>e;e++)if(!this.params[e].name&&!this.params[e].variadic&&a[e].value.eval(b).toCSS()!=this.params[e].value.eval(b).toCSS())return!1;return!0}}}(c("../tree")),function(a){a.Negative=function(a){this.value=a},a.Negative.prototype={type:"Negative",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("-"),this.value.genCSS(a,b)},toCSS:a.toCSS,eval:function(b){return b.isMathOn()?new a.Operation("*",[new a.Dimension(-1),this.value]).eval(b):new a.Negative(this.value.eval(b))}}}(c("../tree")),function(a){a.Operation=function(a,b,c){this.op=a.trim(),this.operands=b,this.isSpaced=c},a.Operation.prototype={type:"Operation",accept:function(a){this.operands=a.visit(this.operands)},eval:function(b){var c=this.operands[0].eval(b),d=this.operands[1].eval(b);if(b.isMathOn()){if(c instanceof a.Dimension&&d instanceof a.Color&&(c=c.toColor()),d instanceof a.Dimension&&c instanceof a.Color&&(d=d.toColor()),!c.operate)throw{type:"Operation",message:"Operation on an invalid type"};return c.operate(b,this.op,d)}return new a.Operation(this.op,[c,d],this.isSpaced)},genCSS:function(a,b){this.operands[0].genCSS(a,b),this.isSpaced&&b.add(" "),b.add(this.op),this.isSpaced&&b.add(" "),this.operands[1].genCSS(a,b)},toCSS:a.toCSS},a.operate=function(a,b,c,d){switch(b){case"+":return c+d;case"-":return c-d;case"*":return c*d;case"/":return c/d}}}(c("../tree")),function(a){a.Paren=function(a){this.value=a},a.Paren.prototype={type:"Paren",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("("),this.value.genCSS(a,b),b.add(")")},toCSS:a.toCSS,eval:function(b){return new a.Paren(this.value.eval(b))}}}(c("../tree")),function(a){a.Quoted=function(a,b,c,d,e){this.escaped=c,this.value=b||"",this.quote=a.charAt(0),this.index=d,this.currentFileInfo=e},a.Quoted.prototype={type:"Quoted",genCSS:function(a,b){this.escaped||b.add(this.quote,this.currentFileInfo,this.index),b.add(this.value),this.escaped||b.add(this.quote)},toCSS:a.toCSS,eval:function(b){var c=this,d=this.value.replace(/`([^`]+)`/g,function(d,e){return new a.JavaScript(e,c.index,!0).eval(b).value}).replace(/@\{([\w-]+)\}/g,function(d,e){var f=new a.Variable("@"+e,c.index,c.currentFileInfo).eval(b,!0);return f instanceof a.Quoted?f.value:f.toCSS()});return new a.Quoted(this.quote+d+this.quote,d,this.escaped,this.index,this.currentFileInfo)},compare:function(a){if(!a.toCSS)return-1;var b=this.toCSS(),c=a.toCSS();return b===c?0:c>b?-1:1}}}(c("../tree")),function(a){function b(a,b){var c,d="",e=b.length,f={add:function(a){d+=a}};for(c=0;e>c;c++)b[c].eval(a).genCSS(a,f);return d}a.Rule=function(b,c,d,e,f,g,h){this.name=b,this.value=c instanceof a.Value||c instanceof a.Ruleset?c:new a.Value([c]),this.important=d?" "+d.trim():"",this.merge=e,this.index=f,this.currentFileInfo=g,this.inline=h||!1,this.variable=b.charAt&&"@"===b.charAt(0)},a.Rule.prototype={type:"Rule",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add(this.name+(a.compress?":":": "),this.currentFileInfo,this.index);try{this.value.genCSS(a,b)}catch(c){throw c.index=this.index,c.filename=this.currentFileInfo.filename,c}b.add(this.important+(this.inline||a.lastRule&&a.compress?"":";"),this.currentFileInfo,this.index)},toCSS:a.toCSS,eval:function(c){var d,e=!1,f=this.name;"string"!=typeof f&&(f=1===f.length&&f[0]instanceof a.Keyword?f[0].value:b(c,f)),"font"!==f||c.strictMath||(e=!0,c.strictMath=!0);try{if(d=this.value.eval(c),!this.variable&&"DetachedRuleset"===d.type)throw{message:"Rulesets cannot be evaluated on a property.",index:this.index,filename:this.currentFileInfo.filename};return new a.Rule(f,d,this.important,this.merge,this.index,this.currentFileInfo,this.inline)}catch(g){throw"number"!=typeof g.index&&(g.index=this.index,g.filename=this.currentFileInfo.filename),g}finally{e&&(c.strictMath=!1)}},makeImportant:function(){return new a.Rule(this.name,this.value,"!important",this.merge,this.index,this.currentFileInfo,this.inline)}}}(c("../tree")),function(a){a.RulesetCall=function(a){this.variable=a},a.RulesetCall.prototype={type:"RulesetCall",accept:function(){},eval:function(b){var c=new a.Variable(this.variable).eval(b);return c.callEval(b)}}}(c("../tree")),function(a){a.Ruleset=function(a,b,c){this.selectors=a,this.rules=b,this._lookups={},this.strictImports=c},a.Ruleset.prototype={type:"Ruleset",accept:function(a){this.paths?a.visitArray(this.paths,!0):this.selectors&&(this.selectors=a.visitArray(this.selectors)),this.rules&&this.rules.length&&(this.rules=a.visitArray(this.rules))},eval:function(b){var c,d,e,f,g=this.selectors,h=a.defaultFunc,i=!1;if(g&&(d=g.length)){for(c=[],h.error({type:"Syntax",message:"it is currently only allowed in parametric mixin guards,"}),f=0;d>f;f++)e=g[f].eval(b),c.push(e),e.evaldCondition&&(i=!0);h.reset()}else i=!0;var j,k,l=this.rules?this.rules.slice(0):null,m=new a.Ruleset(c,l,this.strictImports);m.originalRuleset=this,m.root=this.root,m.firstRoot=this.firstRoot,m.allowImports=this.allowImports,this.debugInfo&&(m.debugInfo=this.debugInfo),i||(l.length=0);var n=b.frames;n.unshift(m);var o=b.selectors;o||(b.selectors=o=[]),o.unshift(this.selectors),(m.root||m.allowImports||!m.strictImports)&&m.evalImports(b);var p=m.rules,q=p?p.length:0;for(f=0;q>f;f++)(p[f]instanceof a.mixin.Definition||p[f]instanceof a.DetachedRuleset)&&(p[f]=p[f].eval(b));var r=b.mediaBlocks&&b.mediaBlocks.length||0;for(f=0;q>f;f++)p[f]instanceof a.mixin.Call?(l=p[f].eval(b).filter(function(b){return b instanceof a.Rule&&b.variable?!m.variable(b.name):!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache()):p[f]instanceof a.RulesetCall&&(l=p[f].eval(b).rules.filter(function(b){return b instanceof a.Rule&&b.variable?!1:!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache());for(f=0;fb;b++)c=g[b],(c instanceof d||c instanceof e)&&f.push(c);return f},prependRule:function(a){var b=this.rules;b?b.unshift(a):this.rules=[a]},find:function(b,c){c=c||this;var d,e=[],f=b.toCSS();return f in this._lookups?this._lookups[f]:(this.rulesets().forEach(function(f){if(f!==c)for(var g=0;gd?Array.prototype.push.apply(e,f.find(new a.Selector(b.elements.slice(d)),c)):e.push(f);break}}),this._lookups[f]=e,e)},genCSS:function(b,c){var d,e,f,g,h,i,j=[],k=[];b.tabLevel=b.tabLevel||0,this.root||b.tabLevel++;var l,m=b.compress?"":Array(b.tabLevel+1).join(" "),n=b.compress?"":Array(b.tabLevel).join(" ");for(d=0;dd;d++)if(i=p[d],o=i.length)for(d>0&&c.add(l),b.firstSelector=!0,i[0].genCSS(b,c),b.firstSelector=!1,e=1;o>e;e++)i[e].genCSS(b,c);c.add((b.compress?"{":" {\n")+m)}for(d=0;dd;d++)l&&c.add(l),k[d].genCSS(b,c);c.isEmpty()||b.compress||!this.firstRoot||c.add("\n")},toCSS:a.toCSS,markReferenced:function(){if(this.selectors)for(var a=0;a0&&this.mergeElementsOnToSelectors(r,i),f=0;f0&&(k[0].elements=k[0].elements.slice(0),k[0].elements.push(new a.Element(j.combinator,"",j.index,j.currentFileInfo))),s.push(k);else for(g=0;g0?(m=k.slice(0),q=m.pop(),o=d.createDerived(q.elements.slice(0)),p=!1):o=d.createDerived([]),l.length>1&&(n=n.concat(l.slice(1))),l.length>0&&(p=!1,o.elements.push(new a.Element(j.combinator,l[0].elements[0].value,j.index,j.currentFileInfo)),o.elements=o.elements.concat(l[0].elements.slice(1))),p||m.push(o),m=m.concat(n),s.push(m);i=s,r=[]}for(r.length>0&&this.mergeElementsOnToSelectors(r,i),e=0;e0&&b.push(i[e])}else if(c.length>0)for(e=0;e0?e[e.length-1]=e[e.length-1].createDerived(e[e.length-1].elements.concat(b)):e.push(new a.Selector(b))}}}(c("../tree")),function(a){a.Selector=function(a,b,c,d,e,f){this.elements=a,this.extendList=b,this.condition=c,this.currentFileInfo=e||{},this.isReferenced=f,c||(this.evaldCondition=!0)},a.Selector.prototype={type:"Selector",accept:function(a){this.elements&&(this.elements=a.visitArray(this.elements)),this.extendList&&(this.extendList=a.visitArray(this.extendList)),this.condition&&(this.condition=a.visit(this.condition))},createDerived:function(b,c,d){d=null!=d?d:this.evaldCondition;var e=new a.Selector(b,c||this.extendList,null,this.index,this.currentFileInfo,this.isReferenced);return e.evaldCondition=d,e.mediaEmpty=this.mediaEmpty,e},match:function(a){var b,c,d=this.elements,e=d.length;if(a.CacheElements(),b=a._elements.length,0===b||b>e)return 0;for(c=0;b>c;c++)if(d[c].value!==a._elements[c])return 0;return b},CacheElements:function(){var a,b,c,d="";if(!this._elements){for(a=this.elements.length,c=0;a>c;c++)if(b=this.elements[c],d+=b.combinator.value,b.value.value){if("string"!=typeof b.value.value){d="";break}d+=b.value.value}else d+=b.value;this._elements=d.match(/[,&#\.\w-]([\w-]|(\\.))*/g),this._elements?"&"===this._elements[0]&&this._elements.shift():this._elements=[]}},isJustParentSelector:function(){return!this.mediaEmpty&&1===this.elements.length&&"&"===this.elements[0].value&&(" "===this.elements[0].combinator.value||""===this.elements[0].combinator.value)},eval:function(a){var b=this.condition&&this.condition.eval(a),c=this.elements,d=this.extendList;return c=c&&c.map(function(b){return b.eval(a)}),d=d&&d.map(function(b){return b.eval(a)}),this.createDerived(c,d,b)},genCSS:function(a,b){var c,d;if(a&&a.firstSelector||""!==this.elements[0].combinator.value||b.add(" ",this.currentFileInfo,this.index),!this._css)for(c=0;cc;c++)this.visit(a[c]);return a}var e=[];for(c=0;d>c;c++){var f=this.visit(a[c]);f.splice?f.length&&this.flatten(f,e):e.push(f)}return e},flatten:function(a,b){b||(b=[]);var c,d,e,f,g,h;for(d=0,c=a.length;c>d;d++)if(e=a[d],e.splice)for(g=0,f=e.length;f>g;g++)h=e[g],h.splice?h.length&&this.flatten(h,b):b.push(h);else b.push(e);return b}}}(c("./tree")),function(a){a.importVisitor=function(b,c,d,e,f){if(this._visitor=new a.visitor(this),this._importer=b,this._finish=c,this.env=d||new a.evalEnv,this.importCount=0,this.onceFileDetectionMap=e||{},this.recursionDetector={},f)for(var g in f)f.hasOwnProperty(g)&&(this.recursionDetector[g]=!0)},a.importVisitor.prototype={isReplacing:!0,run:function(a){var b;try{this._visitor.visit(a)}catch(c){b=c}this.isFinished=!0,0===this.importCount&&this._finish(b)},visitImport:function(b,c){var d,e=this,f=b.options.inline;if(!b.css||f){try{d=b.evalForImport(this.env)}catch(g){g.filename||(g.index=b.index,g.filename=b.currentFileInfo.filename),b.css=!0,b.error=g}if(d&&(!d.css||f)){b=d,this.importCount++;var h=new a.evalEnv(this.env,this.env.frames.slice(0));b.options.multiple&&(h.importMultiple=!0),this._importer.push(b.getPath(),b.currentFileInfo,b.options,function(c,d,g,i){c&&!c.filename&&(c.index=b.index,c.filename=b.currentFileInfo.filename),h.importMultiple||(b.skip=g?!0:function(){return i in e.onceFileDetectionMap?!0:(e.onceFileDetectionMap[i]=!0,!1)});var j=function(a){e.importCount--,0===e.importCount&&e.isFinished&&e._finish(a)};if(d){b.root=d,b.importedFilename=i;var k=g||i in e.recursionDetector;if(!f&&(h.importMultiple||!k))return e.recursionDetector[i]=!0,void new a.importVisitor(e._importer,j,h,e.onceFileDetectionMap,e.recursionDetector).run(d)}j()})}}return c.visitDeeper=!1,b},visitRule:function(a,b){return b.visitDeeper=!1,a},visitDirective:function(a){return this.env.frames.unshift(a),a},visitDirectiveOut:function(){this.env.frames.shift()},visitMixinDefinition:function(a){return this.env.frames.unshift(a),a},visitMixinDefinitionOut:function(){this.env.frames.shift()},visitRuleset:function(a){return this.env.frames.unshift(a),a},visitRulesetOut:function(){this.env.frames.shift()},visitMedia:function(a){return this.env.frames.unshift(a.ruleset),a},visitMediaOut:function(){this.env.frames.shift()}}}(c("./tree")),function(a){a.joinSelectorVisitor=function(){this.contexts=[[]],this._visitor=new a.visitor(this)},a.joinSelectorVisitor.prototype={run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){var b,c=this.contexts[this.contexts.length-1],d=[];this.contexts.push(d),a.root||(b=a.selectors,b&&(b=b.filter(function(a){return a.getIsOutput()}),a.selectors=b.length?b:b=null,b&&a.joinSelectors(d,c,b)),b||(a.rules=null),a.paths=d)},visitRulesetOut:function(){this.contexts.length=this.contexts.length-1},visitMedia:function(a){var b=this.contexts[this.contexts.length-1];a.rules[0].root=0===b.length||b[0].multiMedia}}}(c("./tree")),function(a){a.toCSSVisitor=function(b){this._visitor=new a.visitor(this),this._env=b},a.toCSSVisitor.prototype={isReplacing:!0,run:function(a){return this._visitor.visit(a)},visitRule:function(a){return a.variable?[]:a},visitMixinDefinition:function(a){return a.frames=[],[]},visitExtend:function(){return[]},visitComment:function(a){return a.isSilent(this._env)?[]:a},visitMedia:function(a,b){return a.accept(this._visitor),b.visitDeeper=!1,a.rules.length?a:[]},visitDirective:function(b){if(b.currentFileInfo.reference&&!b.isReferenced)return[];if("@charset"===b.name){if(this.charset){if(b.debugInfo){var c=new a.Comment("/* "+b.toCSS(this._env).replace(/\n/g,"")+" */\n");return c.debugInfo=b.debugInfo,this._visitor.visit(c)}return[]}this.charset=!0}return b},checkPropertiesInRoot:function(b){for(var c,d=0;d0)&&e.splice(0,0,b);else{b.paths&&(b.paths=b.paths.filter(function(b){var c;for(" "===b[0].elements[0].combinator.value&&(b[0].elements[0].combinator=new a.Combinator("")),c=0;ch;)d=f[h],d&&d.rules?(e.push(this._visitor.visit(d)),f.splice(h,1),g--):h++;g>0?b.accept(this._visitor):b.rules=null,c.visitDeeper=!1,f=b.rules,f&&(this._mergeRules(f),f=b.rules),f&&(this._removeDuplicateRules(f),f=b.rules),f&&f.length>0&&b.paths.length>0&&e.splice(0,0,b)}return 1===e.length?e[0]:e},_removeDuplicateRules:function(b){if(b){var c,d,e,f={};for(e=b.length-1;e>=0;e--)if(d=b[e],d instanceof a.Rule)if(f[d.name]){c=f[d.name],c instanceof a.Rule&&(c=f[d.name]=[f[d.name].toCSS(this._env)]);var g=d.toCSS(this._env);-1!==c.indexOf(g)?b.splice(e,1):c.push(g)}else f[d.name]=d}},_mergeRules:function(b){if(b){for(var c,d,e,f={},g=0;g1){d=c[0];var h=[],i=[];c.map(function(a){"+"===a.merge&&(i.length>0&&h.push(e(i)),i=[]),i.push(a)}),h.push(e(i)),d.value=g(h)}})}}}}(c("./tree")),function(a){a.extendFinderVisitor=function(){this._visitor=new a.visitor(this),this.contexts=[],this.allExtendsStack=[[]]},a.extendFinderVisitor.prototype={run:function(a){return a=this._visitor.visit(a),a.allExtends=this.allExtendsStack[0],a},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(b){if(!b.root){var c,d,e,f,g=[],h=b.rules,i=h?h.length:0;for(c=0;i>c;c++)b.rules[c]instanceof a.Extend&&(g.push(h[c]),b.extendOnEveryPath=!0);var j=b.paths;for(c=0;c=0||(i=[k.selfSelectors[0]],g=n.findMatch(j,i),g.length&&j.selfSelectors.forEach(function(b){h=n.extendSelector(g,i,b),l=new a.Extend(k.selector,k.option,0),l.selfSelectors=h,h[h.length-1].extendList=[l],m.push(l),l.ruleset=k.ruleset,l.parent_ids=l.parent_ids.concat(k.parent_ids,j.parent_ids),k.firstExtendOnThisSelectorPath&&(l.firstExtendOnThisSelectorPath=!0,k.ruleset.paths.push(h))}));if(m.length){if(this.extendChainCount++,d>100){var o="{unable to calculate}",p="{unable to calculate}";try{o=m[0].selfSelectors[0].toCSS(),p=m[0].selector.toCSS()}catch(q){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+o+":extend("+p+")"}}return m.concat(n.doExtendChaining(m,c,d+1))}return m},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitSelector:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){if(!a.root){var b,c,d,e,f=this.allExtendsStack[this.allExtendsStack.length-1],g=[],h=this;for(d=0;d0&&k[i.matched].combinator.value!==g?i=null:i.matched++,i&&(i.finished=i.matched===k.length,i.finished&&!a.allowAfter&&(e+1j&&k>0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),k=0,j++),i=f.elements.slice(k,h.index).concat([g]).concat(d.elements.slice(1)),j===h.pathIndex&&e>0?l[l.length-1].elements=l[l.length-1].elements.concat(i):(l=l.concat(c.slice(j,h.pathIndex)),l.push(new a.Selector(i))),j=h.endPathIndex,k=h.endPathElementIndex,k>=c[j].elements.length&&(k=0,j++); -return j0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),j++),l=l.concat(c.slice(j,c.length))},visitRulesetOut:function(){},visitMedia:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitMediaOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1},visitDirective:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitDirectiveOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1}}}(c("./tree")),function(a){a.sourceMapOutput=function(a){this._css=[],this._rootNode=a.rootNode,this._writeSourceMap=a.writeSourceMap,this._contentsMap=a.contentsMap,this._contentsIgnoredCharsMap=a.contentsIgnoredCharsMap,this._sourceMapFilename=a.sourceMapFilename,this._outputFilename=a.outputFilename,this._sourceMapURL=a.sourceMapURL,a.sourceMapBasepath&&(this._sourceMapBasepath=a.sourceMapBasepath.replace(/\\/g,"/")),this._sourceMapRootpath=a.sourceMapRootpath,this._outputSourceFiles=a.outputSourceFiles,this._sourceMapGeneratorConstructor=a.sourceMapGenerator||c("source-map").SourceMapGenerator,this._sourceMapRootpath&&"/"!==this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1)&&(this._sourceMapRootpath+="/"),this._lineNumber=0,this._column=0},a.sourceMapOutput.prototype.normalizeFilename=function(a){return a=a.replace(/\\/g,"/"),this._sourceMapBasepath&&0===a.indexOf(this._sourceMapBasepath)&&(a=a.substring(this._sourceMapBasepath.length),("\\"===a.charAt(0)||"/"===a.charAt(0))&&(a=a.substring(1))),(this._sourceMapRootpath||"")+a},a.sourceMapOutput.prototype.add=function(a,b,c,d){if(a){var e,f,g,h,i;if(b){var j=this._contentsMap[b.filename];this._contentsIgnoredCharsMap[b.filename]&&(c-=this._contentsIgnoredCharsMap[b.filename],0>c&&(c=0),j=j.slice(this._contentsIgnoredCharsMap[b.filename])),j=j.substring(0,c),f=j.split("\n"),h=f[f.length-1]}if(e=a.split("\n"),g=e[e.length-1],b)if(d)for(i=0;i0){var d,e=JSON.stringify(this._sourceMapGenerator.toJSON());this._sourceMapURL?d=this._sourceMapURL:this._sourceMapFilename&&(d=this.normalizeFilename(this._sourceMapFilename)),this._writeSourceMap?this._writeSourceMap(e):d="data:application/json,"+encodeURIComponent(e),d&&this._css.push("/*# sourceMappingURL="+d+" */")}return this._css.join("")}}(c("./tree"));var y=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);w.env=w.env||("127.0.0.1"==location.hostname||"0.0.0.0"==location.hostname||"localhost"==location.hostname||location.port&&location.port.length>0||y?"development":"production");var z={debug:3,info:2,errors:1,none:0};if(w.logLevel="undefined"!=typeof w.logLevel?w.logLevel:"development"===w.env?z.debug:z.errors,w.async=w.async||!1,w.fileAsync=w.fileAsync||!1,w.poll=w.poll||(y?1e3:1500),w.functions)for(var A in w.functions)w.functions.hasOwnProperty(A)&&(w.tree.functions[A]=w.functions[A]);var B=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);B&&(w.dumpLineNumbers=B[1]);var C=/^text\/(x-)?less$/,D=null,E={};if(w.watch=function(){return w.watchMode||(w.env="development",v()),this.watchMode=!0,!0},w.unwatch=function(){return clearInterval(w.watchTimer),this.watchMode=!1,!1},/!watch/.test(location.hash)&&w.watch(),"development"!=w.env)try{D="undefined"==typeof a.localStorage?null:a.localStorage}catch(F){}var G=document.getElementsByTagName("link");w.sheets=[];for(var H=0;H=0.4.22", "underscore": "~1.7.0" + }, + "dependencies": { + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + } } }, "httpreq": { @@ -7490,7 +7650,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "dev": true, "requires": { "agent-base": "^4.1.0", @@ -7516,7 +7676,7 @@ }, "iconv-lite": { "version": "0.4.11", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=", "dev": true }, @@ -7940,7 +8100,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -8084,7 +8244,7 @@ "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha1-ezUbjo7dTTmV1NBmaA5mTZRpaCQ=", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", "dev": true, "optional": true }, @@ -8130,7 +8290,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -8143,7 +8303,7 @@ "is-path-in-cwd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha1-WsSLNF72dTOb1sekipEhELJBz1I=", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { "is-path-inside": "^1.0.0" @@ -8231,7 +8391,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -8366,6 +8526,38 @@ "logalot": "^2.0.0" } }, + "jquery": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", + "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=" + }, + "jquery-migrate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", + "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" + }, + "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-validation": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.17.0.tgz", + "integrity": "sha512-XddiAwhGdWhcIJ+W3ri3KG8uTPMua4TPYuUIC8/E7lOyqdScG5xHuy9YishlKc0c/lIQai77EX7hxMdTSYCEjA==", + "requires": { + "jquery": "^1.7 || ^2.0 || ^3.1" + } + }, + "jquery-validation-unobtrusive": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.10.tgz", + "integrity": "sha512-z9ZBP/HslaGNKzFSpfLNJoFm2iqPJfE6CKM0H5e9LmKnYTFxErvCFQZomOLiTmLmZi8Wi/otW38cEXExVDha0w==", + "requires": { + "jquery": ">=1.8", + "jquery-validation": ">=1.16" + } + }, "js-base64": { "version": "2.4.9", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", @@ -8416,7 +8608,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify": { @@ -8448,7 +8640,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -8500,7 +8692,7 @@ "karma": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.5.tgz", - "integrity": "sha1-NxDHoucbHEOTE/KDhG2I4E5PkYw=", + "integrity": "sha512-rECezBeY7mjzGUWhFlB7CvPHgkHJLXyUmWg+6vHCEsdWNUTnmiS6jRrIMcJEWgU2DUGZzGWG0bTRVky8fsDTOA==", "dev": true, "requires": { "bluebird": "^3.3.0", @@ -8535,7 +8727,7 @@ "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { "micromatch": "^3.1.4", @@ -8569,7 +8761,7 @@ "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha1-NW/04rDo5D4yLRijckYLvPOszSY=", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -8636,7 +8828,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -8649,7 +8841,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -8667,7 +8859,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "range-parser": { @@ -8679,7 +8871,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "requires": { "bytes": "3.0.0", @@ -8691,7 +8883,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "utils-merge": { @@ -8761,6 +8953,11 @@ "dev": true, "optional": 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", @@ -8856,7 +9053,7 @@ "dependencies": { "iconv-lite": { "version": "0.4.15", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", "dev": true } @@ -8892,7 +9089,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -9204,7 +9401,7 @@ "log4js": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz", - "integrity": "sha1-vzkC7/ZcaSPZzpz70ttUFg40AFo=", + "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==", "dev": true, "requires": { "amqplib": "^0.5.2", @@ -9366,7 +9563,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "optional": true, @@ -9381,7 +9578,7 @@ }, "request": { "version": "2.75.0", - "resolved": "http://registry.npmjs.org/request/-/request-2.75.0.tgz", + "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", "dev": true, "optional": true, @@ -9475,7 +9672,7 @@ "mailgun-js": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.18.1.tgz", - "integrity": "sha1-7jmqGNe7WYpc6e3oSvtoHe/IprA=", + "integrity": "sha512-lvuMP14u24HS2uBsJEnzSyPMxzU2b99tQsIx1o6QNjqxjk8b3WvR+vq5oG1mjqz/IBYo+5gF+uSoDS0RkMVHmg==", "dev": true, "optional": true, "requires": { @@ -9503,7 +9700,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "optional": true, "requires": { @@ -9579,7 +9776,7 @@ }, "marked": { "version": "0.3.2", - "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.2.tgz", "integrity": "sha1-AV2xWIZEOPJKZL3WGgQotBhwbQk=", "dev": true }, @@ -9627,7 +9824,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -9745,7 +9942,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -9759,7 +9956,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -9786,16 +9983,21 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" } }, + "moment": { + "version": "2.10.6", + "resolved": "http://registry.npmjs.org/moment/-/moment-2.10.6.tgz", + "integrity": "sha1-bLIZZ8ecunsMpeZmRPFzZis++nc=" + }, "morgan": { "version": "1.6.1", - "resolved": "http://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", "dev": true, "requires": { @@ -9808,7 +10010,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -9905,6 +10107,11 @@ "dev": true, "optional": 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", @@ -13131,7 +13338,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "optional": true, @@ -13145,7 +13352,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "optional": true, "requires": { @@ -13162,7 +13369,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "optional": true, "requires": { @@ -13177,7 +13384,7 @@ "pac-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha1-auoweH2wqJFwTet4AKcip2FabyY=", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", "dev": true, "optional": true, "requires": { @@ -13388,7 +13595,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -13505,7 +13712,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -13528,7 +13735,7 @@ }, "postcss-calc": { "version": "5.3.1", - "resolved": "http://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", "dev": true, "requires": { @@ -13560,7 +13767,7 @@ }, "postcss-discard-comments": { "version": "2.0.4", - "resolved": "http://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", "dev": true, "requires": { @@ -13578,7 +13785,7 @@ }, "postcss-discard-empty": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", "dev": true, "requires": { @@ -13587,7 +13794,7 @@ }, "postcss-discard-overridden": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", "dev": true, "requires": { @@ -13596,7 +13803,7 @@ }, "postcss-discard-unused": { "version": "2.2.3", - "resolved": "http://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", "dev": true, "requires": { @@ -13647,7 +13854,7 @@ }, "postcss-merge-idents": { "version": "2.1.7", - "resolved": "http://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", "dev": true, "requires": { @@ -13686,7 +13893,7 @@ }, "postcss-minify-font-values": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", "dev": true, "requires": { @@ -13697,7 +13904,7 @@ }, "postcss-minify-gradients": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", "dev": true, "requires": { @@ -13707,7 +13914,7 @@ }, "postcss-minify-params": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", "dev": true, "requires": { @@ -13719,7 +13926,7 @@ }, "postcss-minify-selectors": { "version": "2.1.1", - "resolved": "http://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", "dev": true, "requires": { @@ -13731,7 +13938,7 @@ }, "postcss-normalize-charset": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", "dev": true, "requires": { @@ -13740,7 +13947,7 @@ }, "postcss-normalize-url": { "version": "3.0.8", - "resolved": "http://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", "dev": true, "requires": { @@ -13762,7 +13969,7 @@ }, "postcss-reduce-idents": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", "dev": true, "requires": { @@ -13772,7 +13979,7 @@ }, "postcss-reduce-initial": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", "dev": true, "requires": { @@ -13781,7 +13988,7 @@ }, "postcss-reduce-transforms": { "version": "1.0.4", - "resolved": "http://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", "dev": true, "requires": { @@ -13803,7 +14010,7 @@ }, "postcss-svgo": { "version": "2.1.6", - "resolved": "http://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", "dev": true, "requires": { @@ -13815,7 +14022,7 @@ }, "postcss-unique-selectors": { "version": "2.0.2", - "resolved": "http://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", "dev": true, "requires": { @@ -13832,7 +14039,7 @@ }, "postcss-zindex": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", "dev": true, "requires": { @@ -13939,7 +14146,7 @@ "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "optional": true, "requires": { @@ -13992,7 +14199,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -14061,7 +14268,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true } @@ -14152,7 +14359,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -14203,7 +14410,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14233,7 +14440,7 @@ "redis": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", "dev": true, "optional": true, "requires": { @@ -14245,7 +14452,7 @@ "redis-commands": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha1-RJWIlBTx6IYmEYCxRC5ylWAtg6I=", + "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==", "dev": true, "optional": true }, @@ -14258,7 +14465,7 @@ }, "reduce-css-calc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", "dev": true, "requires": { @@ -14338,7 +14545,7 @@ "regexpp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha1-sqdTSoXKGwM7z1zp/45W1OB1U2U=", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "dev": true }, "regexpu-core": { @@ -14470,7 +14677,7 @@ "requestretry": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", - "integrity": "sha1-IT7BAG7rdQ6LjOVBdig9FajVXZQ=", + "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", "dev": true, "optional": true, "requires": { @@ -14690,7 +14897,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14708,6 +14915,11 @@ "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": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", @@ -14762,7 +14974,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -14832,7 +15044,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -14890,7 +15102,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -14920,6 +15132,14 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "signalr": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/signalr/-/signalr-2.3.0.tgz", + "integrity": "sha512-NrvIGftLz3QVujdjCvaNso56ltTr1FowSR0DCIaSOJ3J4t5pTebTfnh2VT0HHIM3PJ/v15lukIL4y+8MMknqzg==", + "requires": { + "jquery": ">=1.6.4" + } + }, "slack-node": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz", @@ -14939,7 +15159,7 @@ "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" @@ -15120,7 +15340,7 @@ "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha1-7S2l7nnxCVUDbj2kE7/X8eTYbI4=", + "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -15132,7 +15352,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -15159,7 +15379,7 @@ "socks-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha1-WTa/i3B6mTB5xvN9sgkYIb/6ZHM=", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", "dev": true, "requires": { "agent-base": "~4.2.0", @@ -15424,7 +15644,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15456,7 +15676,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15472,7 +15692,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15489,7 +15709,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15527,7 +15747,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -15567,7 +15787,7 @@ }, "strip-dirs": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, "requires": { @@ -15596,7 +15816,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -15676,7 +15896,7 @@ }, "table": { "version": "4.0.3", - "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { @@ -15813,7 +16033,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -15913,6 +16133,11 @@ "dev": true, "optional": true }, + "tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + }, "tiny-lr": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", @@ -15961,7 +16186,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -15976,7 +16201,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true }, @@ -15994,10 +16219,15 @@ } } }, + "tinymce": { + "version": "4.7.13", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.13.tgz", + "integrity": "sha512-6QbNYGV4VExH+p7+o/5km6jOnVSD5mO7aw0s+eKByKnpyG8gZfajxXPhwBM57r7SIravrCI6LFj8DARNe31qPw==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -16171,6 +16401,11 @@ "mime-types": "~2.1.18" } }, + "typeahead.js": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.10.5.tgz", + "integrity": "sha1-HZlxsPRNOF/q2/IsnzadtWKRLeE=" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -16207,7 +16442,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, "unc-path-regex": { @@ -16217,10 +16452,9 @@ "dev": true }, "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true + "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==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", @@ -16371,13 +16605,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16470,7 +16704,7 @@ "uws": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha1-+sg4a+/DOno3BcvVjcR7Qwyk3ZU=", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", "dev": true, "optional": true }, @@ -16713,7 +16947,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -16852,7 +17086,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { "async-limiter": "~1.0.0", @@ -16888,7 +17122,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f7e66f8d24..69c046624f 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,18 +1,50 @@ { + "private": true, "scripts": { - "install": "bower-installer", "test": "karma start test/config/karma.conf.js --singlerun", "build": "gulp" }, "dependencies": { + "ace-builds": "1.3.3", + "angular": "1.7.5", + "angular-animate": "1.7.5", + "angular-cookies": "1.7.5", + "angular-dynamic-locale": "0.1.37", + "angular-i18n": "1.7.5", + "angular-local-storage": "0.7.1", + "angular-messages": "1.7.5", + "angular-mocks": "1.7.5", + "angular-route": "1.7.5", + "angular-sanitize": "1.7.5", + "angular-touch": "1.7.5", + "angular-ui-sortable": "0.15.0", + "animejs": "2.2.0", + "bootstrap-social": "4.8.0", + "clipboard": "2.0.0", + "codemirror": "5.3.0", + "diff": "3.4.0", "flatpickr": "4.5.2", - "npm": "^6.4.1" + "font-awesome": "4.7.0", + "jquery": "2.2.4", + "jquery-migrate": "1.4.0", + "jquery-ui-dist": "1.12.1", + "jquery-validation": "1.17.0", + "jquery-validation-unobtrusive": "3.2.10", + "lazyload-js": "1.0.0", + "moment": "2.10.6", + "ng-file-upload": "12.2.13", + "npm": "^6.4.1", + "signalr": "2.3.0", + "tinymce": "4.7.13", + "typeahead.js": "0.10.5", + "underscore": "1.9.1" }, "devDependencies": { "@babel/core": "^7.1.2", "@babel/preset-env": "^7.1.0", "autoprefixer": "^6.5.0", "bower-installer": "^1.2.0", + "gulp-clean-css": "3.10.0", "cssnano": "^3.7.6", "gulp": "^3.9.1", "gulp-babel": "^8.0.0-beta.2", diff --git a/src/Umbraco.Web.UI.Client/setupgulp.bat b/src/Umbraco.Web.UI.Client/setupgulp.bat index 5cddec3747..6a53e11b71 100644 --- a/src/Umbraco.Web.UI.Client/setupgulp.bat +++ b/src/Umbraco.Web.UI.Client/setupgulp.bat @@ -12,12 +12,11 @@ if /I "%c%" EQU "N" goto :eof :setupgulp call npm install -call npm -g install bower call npm -g install gulp call npm -g install gulp-cli ECHO. ECHO. -ECHO You should now be able to run: gulp build +ECHO You should now be able to run: gulp build or gulp dev ECHO. ECHO. \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index 71b6336220..6c0515906f 100644 Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 7f906ddcc0..5006087ca5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,7 +12,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 7; + var maxSections = 8; //setup scope vars scope.maxSections = maxSections; @@ -46,8 +46,8 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionsWidth = 0; scope.totalSections = scope.sections.length; scope.maxSections = maxSections; - scope.overflowingSections = 0; - scope.needTray = false; + scope.overflowingSections = scope.maxSections - scope.totalSections; + scope.needTray = scope.sections.length > scope.maxSections; // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js index 08e0a44c0b..2dc0ebdf93 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js @@ -1,4 +1,17 @@ -(function() { +/** +@ngdoc directive +@name umbraco.directives.directive:umbTourStep +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour step component is a component that can be used in custom views for tour steps. + +@param {callback} onClose The callback which should be performened when the close button of the tour step is clicked +@param {boolean=} hideClose A boolean indicating if the close button needs to be shown + +**/ +(function () { 'use strict'; function TourStepDirective() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcontent.directive.js index 52ed358b61..909f87eac5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcontent.directive.js @@ -1,4 +1,17 @@ -(function() { +/** +@ngdoc directive +@name umbraco.directives.directive:umbTourStepContent +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour step content component is a component that can be used in custom views for tour steps. +It's meant to be used in the umb-tour-step directive. +All markup in the body of the directive will be shown after the content attribute + +@param {string} content The content that needs to be shown +**/ +(function () { 'use strict'; function TourStepContentDirective() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcounter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcounter.directive.js index 7e04ef5d00..2477953580 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcounter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepcounter.directive.js @@ -1,4 +1,18 @@ -(function() { +/** +@ngdoc directive +@name umbraco.directives.directive:umbTourStepCounter +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour step counter component is a component that can be used in custom views for tour steps. +It's meant to be used in the umb-tour-step-footer directive. It will show the progress you have made in a tour eg. step 2/12 + + +@param {int} currentStep The current step the tour is on +@param {int} totalSteps The current step the tour is on +**/ +(function () { 'use strict'; function TourStepCounterDirective() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepfooter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepfooter.directive.js index fedb527972..7cfb498394 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepfooter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepfooter.directive.js @@ -1,4 +1,16 @@ -(function() { +/** +@ngdoc directive +@name umbraco.directives.directive:umbTourStepFooter +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour step footer component is a component that can be used in custom views for tour steps. It's meant to be used in the umb-tour-step directive. +All markup in the body of the directive will be shown as the footer of the tour step + + +**/ +(function () { 'use strict'; function TourStepFooterDirective() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepheader.directive.js index 9d32ad87a4..ec6768928f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstepheader.directive.js @@ -1,4 +1,16 @@ -(function() { +/** +@ngdoc directive +@name umbraco.directives.directive:umbTourStepHeader +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour step header component is a component that can be used in custom views for tour steps. It's meant to be used in the umb-tour-step directive. + + +@param {string} title The title that needs to be shown +**/ +(function () { 'use strict'; function TourStepHeaderDirective() { 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 aec667077c..cd04f0bb52 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 @@ -46,7 +46,7 @@ $scope.ancestors = anc; }); $scope.$watch('culture', - function(value, oldValue) { + function (value, oldValue) { entityResource.getAncestors(content.id, "document", value) .then(function (anc) { $scope.ancestors = anc; @@ -99,10 +99,21 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if (args && args.documentType && args.documentType.key === content.documentType.key) { + if (args && args.documentType && args.documentType.key === $scope.content.documentType.key) { loadContent(); } })); + + evts.push(eventsService.on("editors.content.reload", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if(args && args.node && args.node.key === $scope.content.key) { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } + })); + } /** @@ -146,7 +157,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -155,7 +166,7 @@ } // create the save button - if(_.contains($scope.content.allowedActions, "A")) { + if (_.contains($scope.content.allowedActions, "A")) { $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; @@ -170,7 +181,8 @@ saveAndPublish: $scope.saveAndPublish, sendToPublish: $scope.sendToPublish, unpublish: $scope.unpublish, - schedulePublish: $scope.schedule + schedulePublish: $scope.schedule, + publishDescendants: $scope.publishDescendants } }); @@ -224,7 +236,7 @@ } } - function checkValidility(){ + function checkValidility() { //Get all controls from the 'contentForm' var allControls = $scope.contentForm.$getControls(); @@ -233,7 +245,7 @@ //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' //Check property - $name === "contentHeaderForm" - allControls = _.filter(allControls, function(obj){ + allControls = _.filter(allControls, function (obj) { return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); }); @@ -251,26 +263,26 @@ } //Controls is the - function recurseFormControls(controls, array){ + function recurseFormControls(controls, array) { //Loop over the controls for (var i = 0; i < controls.length; i++) { var controlItem = controls[i]; //Check if the controlItem has a property '' - if(controlItem.hasOwnProperty('$submitted')){ + if (controlItem.hasOwnProperty('$submitted')) { //This item is a form - so lets get the child controls of it & recurse again var childFormControls = controlItem.$getControls(); recurseFormControls(childFormControls, array); } else { //We can assume its a field on a form - if(controlItem.hasOwnProperty('$error')){ + if (controlItem.hasOwnProperty('$error')) { //Set the validlity of the error/s to be valid //String of keys of error invalid messages var errorKeys = []; - for(var key in controlItem.$error){ + for (var key in controlItem.$error) { errorKeys.push(key); controlItem.$setValidity(key, true); } @@ -286,7 +298,7 @@ return array; } - function resetNestedFieldValiation(array){ + function resetNestedFieldValiation(array) { for (var i = 0; i < array.length; i++) { var item = array[i]; //Item is an object containing two props @@ -294,7 +306,7 @@ var fieldControl = item.control; var fieldErrorKeys = item.errorKeys; - for(var i = 0; i < fieldErrorKeys.length; i++) { + for (var i = 0; i < fieldErrorKeys.length; i++) { fieldControl.$setValidity(fieldErrorKeys[i], false); } } @@ -302,7 +314,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid @@ -409,7 +421,7 @@ }); } - $scope.unpublish = function() { + $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { @@ -421,9 +433,9 @@ submit: function (model) { model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); - var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + var selectedVariants = _.filter(model.variants, function (variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function (variant) { return variant.language.culture; }); contentResource.unpublish($scope.content.id, culturesForUnpublishing) .then(function (data) { @@ -437,8 +449,8 @@ }, function (err) { $scope.page.buttonGroupState = 'error'; }); - - + + }, close: function () { overlayService.close(); @@ -447,7 +459,7 @@ overlayService.open(dialog); } }; - + $scope.sendToPublish = function () { clearNotifications($scope.content); if (showSaveOrPublishDialog()) { @@ -464,7 +476,24 @@ model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response - console.log("saving need to happen here"); + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish", + showNotifications: false + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, function (err) { + clearDirtyState($scope.content.variants); + 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); + }); }, close: function () { overlayService.close(); @@ -476,10 +505,10 @@ } else { $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.sendToPublish, - action: "sendToPublish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -535,10 +564,10 @@ //ensure the publish flag is set $scope.content.variants[0].publish = true; $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.publish, - action: "publish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -593,13 +622,13 @@ else { $scope.page.saveButtonState = "busy"; return performSave({ - saveMethod: $scope.saveMethod(), - action: "save" - }).then(function(){ - $scope.page.saveButtonState = "success"; - }, function () { - $scope.page.saveButtonState = "error"; - }); + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function () { + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; @@ -628,6 +657,29 @@ } }; + $scope.publishDescendants = function() { + clearNotifications($scope.content); + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publishdescendants.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_publishDescendants", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + model.submitButtonState = "success"; + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); + } + }; + $scope.preview = function (content) { @@ -746,7 +798,7 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function(app) { + $scope.appChanged = function (app) { createButtons($scope.content, app); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b2e64983d6..e2292c50d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -101,6 +101,22 @@ scope.node.template = templateAlias; }; + scope.openRollback = function() { + + var rollback = { + node: scope.node, + submit: function(model) { + const args = { node: scope.node }; + eventsService.emit("editors.content.reload", args); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.rollback(rollback); + }; + function loadAuditTrail() { scope.loadingAuditTrail = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index 015255c577..651edf5bfd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -22,14 +22,6 @@ }, link: function(scope) { - function onInit() { - angular.forEach(scope.content.tabs, function (group) { - group.open = true; - }); - } - - onInit(); - }, scope: { content: "=" diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index d56a4d2439..a70b8ca33e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -97,10 +97,32 @@ //listen for the image DOM element loading htmlImage.on("load", function () { $timeout(function () { + + vm.isCroppable = true; + vm.hasDimensions = true; + + if (vm.src) { + if (vm.src.endsWith(".svg")) { + vm.isCroppable = false; + vm.hasDimensions = false; + } + else { + // From: https://stackoverflow.com/a/51789597/5018 + var type = vm.src.substring(vm.src.indexOf("/") + 1, vm.src.indexOf(";base64")); + if (type.startsWith("svg")) { + vm.isCroppable = false; + vm.hasDimensions = false; + } + } + } + setDimensions(); vm.loaded = true; if (vm.onImageLoaded) { - vm.onImageLoaded(); + vm.onImageLoaded({ + "isCroppable": vm.isCroppable, + "hasDimensions": vm.hasDimensions + }); } }, 100); }); @@ -147,19 +169,7 @@ /** Sets the width/height/left/top dimentions based on the image size and the "center" value */ function setDimensions() { - if (vm.src.endsWith(".svg")) { - // svg files don't automatically get a size by - // loading them set a default size for now - vm.dimensions.width = 200; - vm.dimensions.height = 200; - vm.dimensions.left = vm.center.left * vm.dimensions.width - 10; - vm.dimensions.top = vm.center.top * vm.dimensions.height - 10; - // can't crop an svg file, don't show the focal point - if (htmlOverlay) { - htmlOverlay.remove(); - } - } - else if (htmlImage && vm.center) { + if (vm.isCroppable && htmlImage && vm.center) { vm.dimensions.width = htmlImage.width(); vm.dimensions.height = htmlImage.height(); vm.dimensions.left = vm.center.left * vm.dimensions.width - 10; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index 3833dc50b9..c3093eee9e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -31,13 +31,15 @@ angular.module("umbraco.directives") return { restrict: 'E', scope:{ - key: '@' + key: '@', + tokens: '=' }, replace: true, link: function (scope, element, attrs) { var key = scope.key; - localizationService.localize(key).then(function(value){ + var tokens = scope.tokens ? scope.tokens : null; + localizationService.localize(key, tokens).then(function(value){ element.html(value); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 251683edc8..836a5af2a6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -80,6 +80,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // entire tree again since we already still have it in memory. Of course if the section is different we will // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times // since it saves on data retreival and DOM processing. + //TODO: This isn't used!? var lastSection = ""; /** Helper function to emit tree events */ @@ -91,6 +92,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } + //TODO: This isn't used!? function clearCache(section) { treeService.clearCache({ section: section }); } @@ -174,6 +176,29 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } + /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ + function getTreeRootNodes() { + var roots; + if ($scope.tree.root.containsGroups) { + //all children in this case are group nodes, so we want the children of these children + roots = _.reduce( + //get the array of array of children + _.map($scope.tree.root.children, function (n) { + return n.children + }), function (m, p) { + //combine the arrays to one array + return m.concat(p) + }); + } + else { + roots = [$scope.tree.root].concat($scope.tree.root.children); + } + + return _.filter(roots, function (node) { + return node && node.metaData && node.metaData.treeAlias; + }); + } + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { @@ -189,12 +214,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return $scope.activeTree; } - var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children); - $scope.activeTree = _.find(childrenAndSelf, function (node) { - if (node && node.metaData && node.metaData.treeAlias) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - } - return false; + var treeRoots = getTreeRootNodes(); + $scope.activeTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); }); if (!$scope.activeTree) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index 4bc6f08eb9..47d1431e13 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -48,7 +48,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc **/ angular.module("umbraco.directives") - .directive('umbGenerateAlias', function ($timeout, entityResource) { + .directive('umbGenerateAlias', function ($timeout, entityResource, localizationService) { return { restrict: 'E', templateUrl: 'views/components/umb-generate-alias.html', @@ -67,7 +67,21 @@ angular.module("umbraco.directives") var updateAlias = false; scope.locked = true; - scope.placeholderText = "Enter alias..."; + + scope.labels = { + idle: "Enter alias...", + busy: "Generating alias..." + }; + + scope.placeholderText = scope.labels.idle; + + localizationService.localize('placeholders_enterAlias').then(function (value) { + scope.labels.idle = scope.placeholderText = value; + }); + + localizationService.localize('placeholders_generatingAlias').then(function (value) { + scope.labels.busy = value; + }); function generateAlias(value) { @@ -78,7 +92,7 @@ angular.module("umbraco.directives") if( value !== undefined && value !== "" && value !== null) { scope.alias = ""; - scope.placeholderText = "Generating Alias..."; + scope.placeholderText = scope.labels.busy; generateAliasTimeout = $timeout(function () { updateAlias = true; @@ -92,7 +106,7 @@ angular.module("umbraco.directives") } else { updateAlias = true; scope.alias = ""; - scope.placeholderText = "Enter alias..."; + scope.placeholderText = scope.labels.idle; } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index b14f8418c5..308ffbf00f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -125,13 +125,19 @@ Use this directive to generate a thumbnail grid of media items. i--; } - if (scope.includeSubFolders !== 'true') { - if (item.parentId !== parseInt(scope.currentFolderId)) { - scope.items.splice(i, 1); - i--; + + // If subfolder search is not enabled remove the media items that's not needed + // Make sure that includeSubFolder is not undefined since the directive is used + // in contexts where it should not be used. Currently only used when we trigger + // a media picker + if(scope.includeSubFolders !== undefined){ + if (scope.includeSubFolders !== 'true') { + if (item.parentId !== parseInt(scope.currentFolderId)) { + scope.items.splice(i, 1); + i--; + } } } - } @@ -152,7 +158,7 @@ Use this directive to generate a thumbnail grid of media items. } if (!item.isFolder) { - + // handle entity if(item.image) { item.thumbnail = mediaHelper.resolveFileFromEntity(item, true); @@ -161,7 +167,7 @@ Use this directive to generate a thumbnail grid of media items. } else { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); - + var fileProp = _.find(item.properties, function (v) { return (v.alias === "umbracoFile"); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js index c13680a037..d4e77eda05 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js @@ -7,7 +7,7 @@ var formCtrl = ctrls[0]; - if (ctrls.length > 1) { + if (ctrls.length > 1 && ctrls[1]) { //if an ngModel is supplied, assign a render function which is called when the model is changed var modelCtrl = ctrls[1]; var oldRender = modelCtrl.$render; @@ -17,8 +17,9 @@ if (oldRender) { oldRender(); } - }; - } else { + } + } + else { var initValue = attr.umbSetDirtyOnChange; attr.$observe("umbSetDirtyOnChange", function (newValue) { diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js index 28aae834b3..8b1faab448 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/localization.mocks.js @@ -36,7 +36,6 @@ angular.module('umbraco.mocks'). "actions_sendtopublish": "Send To Publish", "actions_sendToTranslate": "Send To Translation", "actions_sort": "Sort", - "actions_toPublish": "Send to publication", "actions_translate": "Translate", "actions_update": "Update", "actions_exportContourForm": "Export form", diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 7ba14485d4..da6f78a6a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -13,8 +13,6 @@ Umbraco.Sys.ServerVariables = { "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/", "macroApiBaseUrl": "/umbraco/Api/Macro/", "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/", - //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff - "legacyTreeJs": "/belle/lib/lazyload/empty.js", "serverVarsJs": "/belle/lib/lazyload/empty.js", "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/", "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/", @@ -39,4 +37,4 @@ Umbraco.Sys.ServerVariables = { assemblyVersion: "1", version: "7" } -}; \ No newline at end of file +}; 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 721cd4da57..64c2b127e9 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 @@ -756,11 +756,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - sendToPublish: function (content, isNew, files) { + sendToPublish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, /** @@ -808,8 +808,106 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to create blueprint from content with id " + contentId ); - } + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns an array of previous version id's, given a node id and a culture + * + * ##usage + *
    +          * contentResource.getRollbackVersions(id, culture)
    +          *    .then(function(versions) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the versions + * + */ + getRollbackVersions: function (contentId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { + contentId: contentId, + culture: culture + }) + ), + "Failed to get rollback versions for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersion + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a previous version of a content item + * + * ##usage + *
    +          * contentResource.getRollbackVersion(versionId, culture)
    +          *    .then(function(version) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the version + * + */ + getRollbackVersion: function (versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { + versionId: versionId, + culture: culture + }) + ), + "Failed to get version for content item with id " + versionId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#rollback + * @methodOf umbraco.resources.contentResource + * + * @description + * Roll backs a content item to a previous version + * + * ##usage + *
    +          * contentResource.rollback(contentId, versionId, culture)
    +          *    .then(function() {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object + * + */ + rollback: function (contentId, versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostRollbackContent", { + contentId: contentId, versionId:versionId, culture:culture + }) + ), + "Failed to roll back content item with id " + contentId + ); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 5d2ac1e8b9..9cf96f8e7a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -109,11 +109,23 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id + { + parentId: args.parentId, + id: args.id }, {responseType: 'text'}), - 'Failed to move media'); + { + error: function(data){ + var errorMsg = 'Failed to move media'; + + if(data.parentId === data.id){ + errorMsg = 'Media can\'t be moved into itself'; + } + + return { + errorMsg: errorMsg + }; + } + }); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index a331af899b..e7f40f4814 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -141,10 +141,6 @@ angular.module('umbraco.services') var self = this; return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - return loadMomentLocaleForCurrentUser(); }); } 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 777b336447..31ff5ff47b 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 @@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (!args.methods) { throw "args.methods is not defined"; } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish) { + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish || !args.methods.publishDescendants) { throw "args.methods does not contain all required defined methods"; } @@ -188,7 +188,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica hotKey: "ctrl+u", hotKeyWhenHidden: true, alias: "unpublish", - addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false" + addEllipsis: "true" }; case "SCHEDULE": //schedule publish - schedule doesn't have a permission letter so @@ -200,6 +200,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica alias: "schedulePublish", addEllipsis: "true" }; + case "PUBLISH_DESCENDANTS": + // Publish descendants - it doesn't have a permission letter so + // the button letter is made unique so it doesn't collide with anything else + return { + letter: ch, + labelKey: "buttons_publishDescendants", + handler: args.methods.publishDescendants, + alias: "publishDescendant", + addEllipsis: "true" + }; default: return null; } @@ -210,7 +220,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //This is the ideal button order but depends on circumstance, we'll use this array to create the button list // Publish, SendToPublish - var buttonOrder = ["U", "H", "SCHEDULE"]; + var buttonOrder = ["U", "H", "SCHEDULE", "PUBLISH_DESCENDANTS"]; //Create the first button (primary button) //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. @@ -253,6 +263,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // get picked up by the loop through permissions if( _.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); + buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } // if we are not creating, then we should add unpublish too, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index eab167c2ec..7d1ef3e9b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -180,6 +180,25 @@ open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#rollback + * @methodOf umbraco.services.editorService + * + * @description + * Opens a rollback editor in infinite editing. + * @param {String} editor.node The node to rollback + * @param {Callback} editor.submit Saves, submits, and closes the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + + function rollback(editor) { + editor.view = "views/common/infiniteeditors/rollback/rollback.html"; + editor.size = "small"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#linkPicker @@ -481,6 +500,7 @@ copy: copy, move: move, embed: embed, + rollback: rollback, linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index cb416e3974..15c738638b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -646,10 +646,10 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q */ hideDialog: function (showMenu) { - setMode("default"); - - if(showMenu){ + if (showMenu) { this.showMenu({ skipDefault: true, node: appState.getMenuState("currentNode") }); + } else { + setMode("default"); } }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index d5d2093d3b..3b605453c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -15,21 +15,21 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent // as a nodeid reference instead of a variable with a getParent() method. var treeCache = {}; - + var standardCssClass = 'icon umb-tree-icon sprTree'; function getCacheKey(args) { //if there is no cache key they return null - it won't be cached. if (!args || !args.cacheKey) { return null; - } + } var cacheKey = args.cacheKey; cacheKey += "_" + args.section; return cacheKey; } - return { + return { /** Internal method to return the tree cache */ _getTreeCache: function() { @@ -70,10 +70,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } }); }, - + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { - //if no level is set, then we make it 1 + //if no level is set, then we make it 1 var childLevel = (level ? level : 1); //set the section if it's not already set if (!parentNode.section) { @@ -91,13 +91,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var funcParent = function() { return parentNode; }; + for (var i = 0; i < treeNodes.length; i++) { var treeNode = treeNodes[i]; treeNode.level = childLevel; - //create a function to get the parent node, we could assign the parent node but + //create a function to get the parent node, we could assign the parent node but // then we cannot serialize this entity because we have a cyclical reference. // Instead we just make a function to return the parentNode. treeNode.parent = funcParent; @@ -108,17 +109,17 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //if there is not route path specified, then set it automatically, //if this is a tree root node then we want to route to the section's dashboard if (!treeNode.routePath) { - + if (treeNode.metaData && treeNode.metaData["treeAlias"]) { //this is a root node - treeNode.routePath = section; + treeNode.routePath = section; } else { var treeAlias = this.getTreeAlias(treeNode); treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; } } - + //now, format the icon data if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); @@ -155,10 +156,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @description * Determines if the current tree is a plugin tree and if so returns the package folder it has declared * so we know where to find it's views, otherwise it will just return undefined. - * + * * @param {String} treeAlias The tree alias to check */ - getTreePackageFolder: function(treeAlias) { + getTreePackageFolder: function(treeAlias) { //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && @@ -167,7 +168,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { return item.alias === treeAlias; }); - + return found ? found.packageFolder : undefined; } return undefined; @@ -181,7 +182,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @description * Clears the tree cache - with optional cacheKey, optional section or optional filter. - * + * * @param {Object} args arguments * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs * @param {String} args.section optional section alias - clear tree for a given section @@ -205,7 +206,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.cacheKey) { throw "args.cacheKey is required if args.childrenOf is supplied"; } - //this will clear out all children for the parentId passed in to this parameter, we'll + //this will clear out all children for the parentId passed in to this parameter, we'll // do this by recursing and specifying a filter var self = this; this.clearCache({ @@ -238,7 +239,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //set the result to the filtered data treeCache[args.cacheKey] = result; } - else { + else { //remove the cache treeCache = _.omit(treeCache, args.cacheKey); } @@ -261,7 +262,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return k.endsWith("_" + args.section); }); treeCache = _.omit(treeCache, toRemove2); - } + } } }, @@ -285,7 +286,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.node) { throw "No node defined on args object for loadNodeChildren"; } - + this.removeChildNodes(args.node); args.node.loading = true; @@ -312,7 +313,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //in case of error, emit event eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); - //stop show the loading indicator + //stop show the loading indicator args.node.loading = false; //tell notications about the error @@ -342,9 +343,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS throw "Cannot remove a node that doesn't have a parent"; } //remove the current item from it's siblings - treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); + treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); }, - + /** * @ngdoc method * @name umbraco.services.treeService#removeChildNodes @@ -352,7 +353,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Removes all child nodes from a given tree node + * Removes all child nodes from a given tree node * @param {object} treeNode the node to remove children from */ removeChildNodes : function(treeNode) { @@ -426,7 +427,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (found) { return found; } - + //check each child of this node if (!treeNode.children) { return null; @@ -442,7 +443,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } } - + //not found return found === undefined ? null : found; }, @@ -464,9 +465,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //all root nodes have metadata key 'treeAlias' var root = null; - var current = treeNode; + var current = treeNode; while (root === null && current) { - + if (current.metaData && current.metaData["treeAlias"]) { root = current; } @@ -491,7 +492,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node + * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node * @param {object} treeNode to retrive tree alias from */ getTreeAlias : function(treeNode) { @@ -509,7 +510,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * gets the tree, returns a promise + * gets the tree, returns a promise * @param {object} args Arguments * @param {string} args.section Section alias * @param {string} args.cacheKey Optional cachekey @@ -525,7 +526,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } var cacheKey = getCacheKey(args); - + //return the cache if it exists if (cacheKey && treeCache[cacheKey] !== undefined) { return $q.when(treeCache[cacheKey]); @@ -540,9 +541,20 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS alias: args.section, root: data }; - //we need to format/modify some of the node data to be used in our app. + + //format the root self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); + //if this is a root that contains group nodes, we need to format those manually too + if (result.root.containsGroups) { + for (var i = 0; i < result.root.children.length; i++) { + var group = result.root.children[i]; + + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(group, group.children, args.section); + } + } + //cache this result if a cache key is specified - generally a cache key should ONLY // be specified for application trees, dialog trees should not be cached. if (cacheKey) { @@ -584,7 +596,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return data; }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#getChildren @@ -592,7 +604,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the children from the server for a given node + * Gets the children from the server for a given node * @param {object} args Arguments * @param {object} args.node tree node object to retrieve the children for * @param {string} args.section current section alias @@ -618,7 +630,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return $q.when(data); }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#reloadNode @@ -639,7 +651,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!node.section) { throw "cannot reload a single node without an assigned node.section"; } - + //set the node to loading node.loading = true; @@ -663,7 +675,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //just update as per normal - this means styles, etc.. won't be applied _.extend(node.parent().children[index], found); } - + //set the node loading node.parent().children[index].loading = false; //return @@ -684,22 +696,25 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * This will return the current node's path by walking up the tree + * This will return the current node's path by walking up the tree * @param {object} node Tree node to retrieve path for */ getPath: function(node) { if (!node) { - throw "node cannot be null"; + throw "node cannot be null"; } if (!angular.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; } - //all root nodes have metadata key 'treeAlias' + var reversePath = []; var current = node; while (current != null) { - reversePath.push(current.id); - if (current.metaData && current.metaData["treeAlias"]) { + reversePath.push(current.id); + + //all tree root nodes (non group, not section root) have a treeAlias so exit if that is the case + //or exit if we cannot traverse further up + if ((current.metaData && current.metaData["treeAlias"]) || !current.parent) { current = null; } else { @@ -708,9 +723,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } return reversePath.reverse(); }, - + syncTree: function(args) { - + if (!args) { throw "No args object defined for syncTree"; } @@ -733,7 +748,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!root) { throw "Could not get the root tree node based on the node passed in"; } - + //now we want to loop through the ids in the path, first we'll check if the first part //of the path is the root node, otherwise we'll search it's children. var currPathIndex = 0; @@ -748,7 +763,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS currPathIndex = 1; } } - + //now that we have the first id to lookup, we can start the process var self = this; @@ -778,7 +793,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } else { - //couldn't find it in the + //couldn't find it in the return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { //ok, got the children, let's find it var found = self.getChildNode(node, args.path[currPathIndex]); @@ -810,7 +825,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return doSync(); } - + }; } 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 1619ca0623..1f99659389 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 @@ -126,12 +126,21 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ /** The default error callback used if one is not supplied in the opts */ function defaultError(data, status, headers, config) { - return { + + var err = { //NOTE: the default error message here should never be used based on the above docs! errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), data: data, status: status }; + + // if "opts" is a promise, we set "err.errorMsg" to be that promise + if (typeof(opts) == "object" && typeof(opts.then) == "function") { + err.errorMsg = opts; + } + + return err; + } //create the callbacs based on whats been passed in. diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 1f6f2c75b8..2912755ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -184,13 +184,17 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Listen for section state changes evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - var f = args; - if (args.value.root && args.value.root.metaData.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; + if (args.key === "currentRootNode") { + + //if the changed state is the currentRootNode, determine if this is a full screen app + if (args.value.root && args.value.root.containsTrees === false) { + $rootScope.emptySection = true; + } + else { + $rootScope.emptySection = false; + } } + })); //Listen for section state changes @@ -422,7 +426,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //this reacts to the options item in the tree //TODO: migrate to nav service - //TODO: is this used? + //TODO: is this used? $scope.searchShowMenu = function (ev, args) { //always skip default args.skipDefault = true; diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index bd354efc90..e15cf0ab62 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 5fb9d9bda8..1f916c6c00 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -1,5 +1,5 @@
    -

    Install Umbraco 8 (alpha)

    +

    Install Umbraco 8 Preview (Editor Track - what does that mean?)

    Enter your name, email and password to install Umbraco 8 with its default settings, alternatively you can customize your installation

    diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 7ed2abc898..a7b4bd0011 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -167,6 +167,7 @@ body.umb-drawer-is-visible #mainwrapper{ @media (min-width: 1101px) { #contentwrapper, #umb-notifications-wrapper {left: 360px;} #speechbubble {left: 360px;} + .emptySection #contentwrapper {left:0px;} } //empty section modification diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index ac8279e3ab..3100433a4a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -66,6 +66,7 @@ // Belle styles @import "buttons.less"; @import "forms.less"; +@import "legacydialog.less"; @import "modals.less"; @import "panel.less"; @import "sections.less"; @@ -159,6 +160,7 @@ @import "components/umb-mini-editor.less"; @import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-details.less"; @import "components/users/umb-user-group-picker-list.less"; @import "components/users/umb-user-group-preview.less"; @import "components/users/umb-user-preview.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index b1e9671de3..0b21864127 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -63,9 +63,6 @@ .btn-group>.btn+.dropdown-toggle { box-shadow: none; -webkit-box-shadow:none; -} - -.btn-group .btn.dropdown-toggle { border-left-width: 1px; border-left-style: solid; border-color: rgba(0,0,0,0.09); diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 78c1616bc6..cbb38a23b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -81,9 +81,9 @@ a, a:hover{ .wait { display: block; - height: 280px; + height: 100%; width: 100%; - background: center center url(../img/loader.gif) no-repeat; + background:#fff center center url(../img/loader.gif) no-repeat; } /****************************/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less index cc024ca5bb..2c7f07ef26 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-drawer.less @@ -159,6 +159,7 @@ margin-bottom: 1px; border-radius: 0; border-bottom: 1px solid @gray-9; + padding: 10px; } .umb-help-list-item:last-child { @@ -193,6 +194,7 @@ .umb-help-list-item__title { font-size: 14px; display: block; + margin-left: 26px; } .umb-help-list-item__description { @@ -205,6 +207,7 @@ margin-right: 8px; color: @gray-4; font-size: 18px; + float: left; } .umb-help-list-item__open-icon { @@ -219,4 +222,4 @@ [data-element*="tour-"].umb-help-list-item:hover .umb-help-list-item__title { text-decoration:none; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4b670ab781..d3f2fee5d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -104,7 +104,7 @@ } .umb-button--xs { - padding: 5px 16px; + padding: 5px 13px; font-size: 14px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 73f059b4ee..150963cbb2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -10,6 +10,10 @@ } } +.umb-toggle:focus .umb-toggle__toggle{ + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); +} + .umb-toggle__handler { position: absolute; top: 0; @@ -30,6 +34,7 @@ background: @gray-8; border-radius: 90px; position: relative; + transition: box-shadow .3s; } .umb-toggle--checked .umb-toggle__toggle { @@ -43,7 +48,6 @@ /* Labels */ .umb-toggle__label { - font-size: 12px; color: @gray-2; } 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 e8b8325183..a302ba71b8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -101,11 +101,13 @@ } .umb-card-grid li.-four-in-row { - flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; } .umb-card-grid li.-three-in-row { flex: 0 0 33.33%; + max-width:33.33%; } .umb-card-grid .umb-card-grid-item { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index f2cacc26b3..fb83504a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -8,6 +8,9 @@ .umb-box-header { padding: 10px 20px; border-bottom: 1px solid @gray-9; + display: flex; + align-items: center; + justify-content: space-between; } .umb-box-header-title { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index a987c5daa3..c6fdf8a7bf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -3,8 +3,8 @@ flex-flow: row wrap; .umb-color-box { - border: none; - color: white; + border: 1px solid @gray-8; + color: @white; cursor: pointer; padding: 1px; text-align: center; @@ -19,7 +19,7 @@ justify-content: center; &:hover, &:focus { - box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } &.umb-color-box--m { @@ -50,7 +50,7 @@ padding-top: 10px; .umb-color-box__label { - background: #fff; + background: @white; font-size: 14px; display: flex; flex-flow: column wrap; @@ -63,7 +63,8 @@ margin-right: -1px; text-indent: 0; text-align: left; - border: 1px solid @gray-8; + border-top: 1px solid @gray-8; + border-bottom: 1px solid @gray-8; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; overflow: hidden; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 21f59a3e2d..52cc7a9aaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,4 +1,17 @@ -.umb-multiple-textbox .textbox-wrapper { +.umb-multiple-textbox{ + &__confirm{ + position: relative; + + &-action{ + margin: 0; + padding: 2px; + background: transparent; + border: 0 none; + } + } +} + +.umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; } @@ -7,7 +20,7 @@ margin-bottom: 0; } -.umb-multiple-textbox .textbox-wrapper i { +.umb-multiple-textbox .textbox-wrapper i:not(.icon-delete, .icon-check) { margin-right: 5px; } 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 514a73407c..5a11403bb3 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 @@ -1,5 +1,6 @@ .umb-nested-content { text-align: center; + position: relative; } .umb-nested-content--not-supported { @@ -208,11 +209,25 @@ width: 99%; } -.usky-grid.umb-nested-content__node-type-picker .cell-tools-menu { - position: relative; - transform: translate(-50%, -25%); -} +.usky-grid.umb-nested-content__node-type-picker { + .cell-tools-menu { + position: relative; + transform: translate(-50%, -25%); + } + .elements li { + &:hover { + i { + color: @white !important; + } + } + + i { + // make sure the item icons shown are in the correct color according to their doc type icon instead of the grid editor item color + color: unset; + } + } +} // this resolves the layout issue introduced in nested content in 7.12 with the addition of the input for link anchors // the attribute selector ensures the change only applies to the linkpicker overlay diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index 7acf47d22e..de2dca5f91 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -1,18 +1,13 @@ .umb-user-cards { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: -10px; + display: grid; + grid-gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } .umb-user-card { - padding: 10px; box-sizing: border-box; - flex: 0 0 100%; max-width: 100%; display: flex; - flex-wrap: wrap; - flex-direction: column; } .umb-user-card:hover, @@ -21,49 +16,6 @@ text-decoration: none !important; } -@media (min-width: 768px) { - .umb-user-card { - flex: 0 0 50%; - max-width: 50%; - } -} - -@media (min-width: 1200px) { - .umb-user-card { - flex: 0 0 33.33%; - max-width: 33.33%; - } -} - -@media (min-width: 1400px) { - .umb-user-card { - flex: 0 0 25%; - max-width: 25%; - } -} - -@media (min-width: 1700px) { - .umb-user-card { - flex: 0 0 20%; - max-width: 20%; - } -} - - -@media (min-width: 1900px) { - .umb-user-card { - flex: 0 0 16.66%; - max-width: 16.66%; - } -} - -@media (min-width: 2200px) { - .umb-user-card { - flex: 0 0 14.28%; - max-width: 14.28%; - } -} - .umb-user-card__content { position: relative; padding: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less new file mode 100644 index 0000000000..9ddad03b48 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less @@ -0,0 +1,114 @@ +.umb-user-details-avatar { + margin-bottom: 20px; + padding-bottom: 20px; + border-bottom: 1px solid #d8d7d9; +} + +div.umb-user-details-actions > div { + margin-bottom: 20px; +} + +.umb-user-details-actions .umb-button { + margin-bottom: 20px; +} + +.umb-user-details-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-user-details-view-wrapper { + padding: 20px 60px; +} + +@media (max-width: 768px) { + + .umb-user-details-view-wrapper { + padding: 0; + } +} + +.umb-user-details-section { + margin-bottom: 40px; +} +.umb-user-details-details { + display: flex; +} + +a.umb-user-details-details__back-link { + font-weight: bold; + color: @black; +} + +.umb-user-details-details__back-link:hover { + color: @gray-4; + text-decoration: none; +} + + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-user-details-details__main-content { + flex: 1 1 auto; + margin-right: 30px; + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'); // Make sure that the main content area doesn't gets affected by inline styling +} + +.umb-user-details-details__main-content .umb-node-preview-add { + max-width: 100%; +} + + +.umb-user-details-details__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-user-details-details { + flex-direction: column; + } + + .umb-user-details-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-user-details-details__sidebar { + flex: 1 1 auto; + width: 100%; + } +} + +.umb-user-details-details__section-title { + font-size: 17px; + font-weight: bold; + color: @black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-user-details-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-user-details-details__information-item { + margin-bottom: 10px; + font-size: 13px; +} + +.umb-user-details-details__information-item-label { + color: @black; + font-weight: bold; +} + +.umb-user-details-details__information-item-content { + word-break: break-word; +} + diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 6940987290..e3350b4956 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -580,7 +580,7 @@ div.help { text-align: center; text-shadow: 0 1px 0 @white; background-color: @gray-10; - border: 1px solid @gray-8; + border: 1px solid @purple-l3; } .add-on, .btn, diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 22e2eb4566..cd32c64782 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -202,7 +202,7 @@ pre { //font-size: @baseFontSize - 1; // 14px to 13px color: @gray-2; line-height: @baseLineHeight; - white-space: pre-line; // 1 + white-space: pre-wrap; // 1 overflow-x: auto; // 1 background-color: @gray-10; border: 1px solid @gray-8; @@ -222,4 +222,4 @@ pre { background-color: transparent; border: 0; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/legacydialog.less b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less new file mode 100644 index 0000000000..ca920d22c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less @@ -0,0 +1,58 @@ + +.umb-dialog .propertyItemheader { + width: 140px !Important; +} + +.umb-dialog .diffDropdown { + width: 400px; +} + +.umb-dialog .diffPanel { + height: 400px; +} + + +.umb-dialog .diff { + margin-top: 10px; + height: 100%; + overflow: auto; + border-top: 1px solid #ccc; + border-top: 1px solid #ccc; + padding: 5px; +} + +.umb-dialog .diff table { + width: 95%; + max-width: 95%; + margin: 0 3px; +} + +.umb-dialog .diff table th { + padding: 5px; + width: 25%; + border-bottom: 1px solid #ccc; +} + +.umb-dialog .diff table td { + border-bottom: 1px solid #ccc; + padding: 3px; +} + +.umb-dialog .diff del { + background: rgb(255, 230, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff ins { + background: rgb(230, 255, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff .diffnotice { + text-align: center; + margin-bottom: 10px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index def6d114ff..912e7d90e2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -52,6 +52,16 @@ -webkit-flex: 1; -ms-flex: 1; flex: 1; + + &__toggle{ + margin: 10px 0; + + label{ + margin-right: 10px; + position: relative; + top: -2px; + } + } } .upload-button { 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 43f5aa5a9a..27a51bc80c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -2,7 +2,12 @@ // Container styles // -------------------------------------------------- .umb-property-editor { - min-width:66.6%; + @media (max-width: 800px) { + width: 100%; + } + @media (min-width: 800px) { + min-width:66.6%; + } &-pull { float:left; @@ -115,6 +120,13 @@ div.umb-codeeditor .umb-btn-toolbar { // // RTE // -------------------------------------------------- +.umb-rte { + position: relative; + + .-loading { + position: absolute; + } +} .mce-tinymce{border: 1px solid @gray-8 !important; border-radius: 0px !important;} .mce-panel{background: @gray-10 !important; border-color: @gray-8 !important;} .mce-btn-group, .mce-btn{border: none !important; background: none !important;} @@ -137,11 +149,14 @@ div.umb-codeeditor .umb-btn-toolbar { /* pre-value editor */ .control-group.color-picker-preval { .thumbnail { - width: 36px; + width: 34px; + height: 34px; min-width: auto; border: none; cursor: move; border-radius: 3px; + margin-top: auto; + margin-bottom: auto; } .handle { @@ -153,19 +168,19 @@ div.umb-codeeditor .umb-btn-toolbar { div.color-picker-prediv { display: inline-flex; align-items: center; - max-width: 85%; + max-width: 100%; + flex: 1; pre { display: inline-flex; font-family: monospace; - margin-right: 10px; - margin-left: 10px; + margin-left: 15px; + margin-right: 15px; white-space: nowrap; overflow: hidden; margin-bottom: 0; vertical-align: middle; - padding-top: 7px; - padding-bottom: 7px; + padding: 6px 10px; background: #f7f7f7; flex: 0 0 auto; } @@ -194,11 +209,11 @@ div.umb-codeeditor .umb-btn-toolbar { label { border: 1px solid #fff; - padding: 7px 10px; + padding: 6px 10px; font-family: monospace; border: 1px solid #dfdfe1; background: #f7f7f7; - margin: 0 15px 0 0; + margin: 0 15px 0 3px; border-radius: 3px; } } @@ -305,7 +320,7 @@ div.umb-codeeditor .umb-btn-toolbar { .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0 5px 5px 0; + margin: 0 0 5px 5px; padding: 5px; } @@ -574,6 +589,16 @@ div.umb-codeeditor .umb-btn-toolbar { } } + .imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + + button { + margin-left: 4px; + } + } + .umb-close-cropper { position: absolute; top: 3px; @@ -838,7 +863,20 @@ div.umb-codeeditor .umb-btn-toolbar { // // Nested boolean (e.g. list view bulk action permissions) -// ---------------------=====----------------------------- +// ------------------------------------------------------- .umb-nested-boolean label {margin-bottom: 8px; float: left; width: 320px;} .umb-nested-boolean label span {float: left; width: 80%;} .umb-nested-boolean label input[type='checkbox'] {margin-right: 10px; float: left;} + +// +// Custom styles of property editors in property preview in document type editor +// ----------------------------------------------------------------------------- +.umb-group-builder__property-preview { + .umb-property-editor { + .slider { + .tooltip { + display: none; + } + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less index b8fb5ca5db..5767b0b474 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less @@ -7,6 +7,8 @@ .ws-normal { white-space: normal; } .nowrap { white-space: nowrap; } .pre { white-space: pre; } +.pre-wrap { white-space: pre-wrap; } +.pre-line { white-space: pre-line; } .truncate { white-space: nowrap; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index c965d16c0d..ac529fc215 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -207,7 +207,7 @@ } } - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 8a7358116a..c0ca65de9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -1,6 +1,6 @@ -
    @@ -30,17 +30,18 @@ no-dirty-check />
    - +
    -
    - + - +
    • @@ -50,7 +51,7 @@
    - + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 9b563be2df..40f5035d11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -151,7 +151,7 @@ angular.module("umbraco") if (folder.id > 0) { entityResource.getAncestors(folder.id, "media") - .then(function(anc) { + .then(function(anc) { $scope.path = _.filter(anc, function(f) { return f.path.indexOf($scope.startNodeId) !== -1; @@ -236,7 +236,10 @@ angular.module("umbraco") $scope.onUploadComplete = function(files) { $scope.gotoFolder($scope.currentFolder).then(function() { if (files.length === 1 && $scope.model.selectedImages.length === 0) { - selectImage($scope.images[$scope.images.length - 1]); + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); } }); }; @@ -305,6 +308,23 @@ angular.module("umbraco") debounceSearchMedia(); }; + /** + * Toggle the $scope.model.allowAsRoot value to either true or false + */ + $scope.toggle = function(){ + + // Make sure to activate the changeSearch function everytime the toggle is clicked + $scope.changeSearch(); + + // Toggle the showChilds option + if($scope.showChilds){ + $scope.showChilds = false; + return; + } + + $scope.showChilds = true; + } + $scope.changePagination = function(pageNumber) { $scope.loading = true; $scope.searchOptions.pageNumber = pageNumber; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 5da564d41f..7a9ccd9b89 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -30,12 +30,15 @@ ng-change="changeSearch()" type="text" no-dirty-check /> -
    - + +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js new file mode 100644 index 0000000000..6b8462b583 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -0,0 +1,177 @@ +(function () { + "use strict"; + + function RollbackController($scope, contentResource, localizationService, assetsService) { + + var vm = this; + + vm.rollback = rollback; + vm.changeLanguage = changeLanguage; + vm.changeVersion = changeVersion; + vm.submit = submit; + vm.close = close; + + ////////// + + function onInit() { + + vm.loading = true; + vm.variantVersions = []; + vm.diff = null; + vm.currentVersion = null; + vm.rollbackButtonDisabled = true; + + // find the current version for invariant nodes + if($scope.model.node.variants.length === 1) { + vm.currentVersion = $scope.model.node.variants[0]; + } + + // find the current version for nodes with variants + if($scope.model.node.variants.length > 1) { + var active = _.find($scope.model.node.variants, function (v) { + return v.active; + }); + + // preselect the language in the dropdown + if(active) { + vm.selectedLanguage = active; + vm.currentVersion = active; + } + } + + // set default title + if(!$scope.model.title) { + localizationService.localize("actions_rollback").then(function(value){ + $scope.model.title = value; + }); + } + + // Load in diff library + assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { + + getVersions().then(function(){ + vm.loading = false; + }); + + }); + + } + + function changeLanguage(language) { + vm.currentVersion = language; + getVersions(); + } + + function changeVersion(version) { + + if(version && version.versionId) { + + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + contentResource.getRollbackVersion(version.versionId, culture) + .then(function(data){ + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); + vm.rollbackButtonDisabled = false; + }); + + } else { + vm.diff = null; + vm.rollbackButtonDisabled = true; + } + } + + function getVersions() { + + const nodeId = $scope.model.node.id; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + vm.previousVersions = data.map(version => { + version.displayValue = version.versionDate + " - " + version.versionAuthorName; + return version; + }); + }); + } + + /** + * This will load in a new version + */ + function createDiff(currentVersion, previousVersion) { + + vm.diff = {}; + vm.diff.properties = []; + + // find diff in name + vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name); + + // extract all properties from the tabs and create new object for the diff + currentVersion.tabs.forEach((tab, tabIndex) => { + tab.properties.forEach((property, propertyIndex) => { + var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; + + // we have to make properties storing values as object into strings (Grid, nested content, etc.) + if(property.value instanceof Object) { + property.value = JSON.stringify(property.value, null, 1); + property.isObject = true; + } + + if(oldProperty.value instanceof Object) { + oldProperty.value = JSON.stringify(oldProperty.value, null, 1); + oldProperty.isObject = true; + } + + // create new property object used in the diff table + var diffProperty = { + "alias": property.alias, + "label": property.label, + "diff": (property.value || oldProperty.value) ? JsDiff.diffWords(property.value, oldProperty.value) : "", + "isObject": (property.isObject || oldProperty.isObject) ? true : false + }; + + vm.diff.properties.push(diffProperty); + + }); + }); + + } + + function rollback() { + + vm.rollbackButtonState = "busy"; + + const nodeId = $scope.model.node.id; + const versionId = vm.previousVersion.versionId; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.rollback(nodeId, versionId, culture) + .then(data => { + vm.rollbackButtonState = "success"; + submit(); + }, error => { + vm.rollbackButtonState = "error"; + }); + + } + + function submit() { + if($scope.model.submit) { + $scope.model.submit($scope.model.submit); + } + } + + function close() { + if($scope.model.close) { + $scope.model.close(); + } + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html new file mode 100644 index 0000000000..f7ab78b7a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -0,0 +1,104 @@ +
    + + + + + + + + + + + + + + +
    +
    + +
    + +
    + +
    +

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

    + +
    + +
    + +
    + +
    Changes
    + + + + + + + + + + + + + +
    Name + + {{part.value}} + {{part.value}} + {{part.value}} + +
    {{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
    + +
    + +
    +
    +
    + + + + + + + + + + +
    + +
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html index fc62485521..bc5c114bb6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html @@ -1,4 +1,4 @@ -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html index 6840e58565..ec93009a4c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/rename.html @@ -7,8 +7,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html index af3b954c83..6543d7a280 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html @@ -12,13 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    -
    - {{currentNode.name}} was copied underneath {{target.name}}
    +
    + {{currentNode.name}} was copied underneath {{target.name}} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 9fae0a6304..5fa70263b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -26,8 +26,8 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.showCreateDocTypeCollection = function () { $scope.model.creatingDoctypeCollection = true; - $scope.model.collectionCreateTemplate = true; - $scope.model.collectionItemCreateTemplate = true; + $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates; + $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates; }; $scope.createContainer = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 917e9147a4..d19b1329d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -16,8 +16,8 @@ - - Document type> + + Document type> @@ -45,8 +45,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    @@ -69,20 +71,26 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    - - + + + + - - + + + + 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 7a1a825e46..10c563a289 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 @@ -9,7 +9,7 @@ (function () { "use strict"; - function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService, angularHelper) { var vm = this; var evts = []; @@ -478,6 +478,15 @@ eventsService.unsubscribe(evts[e]); } }); + + // #3368 - changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty + $scope.$watch("vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.allowedTemplates.length + vm.contentType.isContainer", function (newVal, oldVal) { + if (oldVal === undefined) { + // still initializing, ignore + return; + } + angularHelper.getCurrentForm($scope).$setDirty(); + }); } angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index 379a64fc14..a75c6346c7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -12,13 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    -
    - {{currentNode.name}} was moved underneath {{target.name}}
    +
    + {{currentNode.name}} was moved underneath {{target.name}} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html index f739cadb71..c52b3be6d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/rename.html @@ -6,8 +6,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html index 735bf16d94..dfec56fbc0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html @@ -31,6 +31,7 @@ Language + ISO Default Mandatory Fallback @@ -42,6 +43,9 @@ {{ language.name }} + + {{ language.culture }} + -
    +
    {{ group.label }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html index 8104fcc979..97b21d6645 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html @@ -12,13 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    -
    - {{currentNode.name}} was copied underneath {{target.name}}
    +
    + {{currentNode.name}} was copied underneath {{target.name}} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html index 5d96375b44..d6a8fa4e70 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -12,12 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    -
    {{currentNode.name}} was moved underneath {{target.name}}
    +
    + {{currentNode.name}} was moved underneath {{target.name}} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html index 5f027cf607..0bc4aa5123 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/rename.html @@ -6,8 +6,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index fb9d99a9eb..310559b4d4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -18,7 +18,7 @@ -
    +
    {{ group.label }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 78daeceab4..a3c74c56d1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -77,10 +77,10 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS } else { //so, we usually refernce all editors with the Int ID, but with members we have - //a different pattern, adding a route-redirect here to handle this: - //isNumber doesnt work here since its seen as a string - - //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers. + //a different pattern, adding a route-redirect here to handle this just in case. + //(isNumber doesnt work here since its seen as a string) + //The reason this might be an INT is due to the routing used for the member list view + //but this is now configured to use the key, so this is just a fail safe if ($routeParams.id && $routeParams.id.length < 9) { diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js index 9fc03349e8..b099396142 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js @@ -39,7 +39,7 @@ function MemberListController($scope, $routeParams, $location, $q, $window, appS // route but there might be server validation errors in the collection which we need to display // after the redirect, so we will bind all subscriptions which will show the server validation errors // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); $scope.page.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html index b7fcf00bba..36ab0e71c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html @@ -61,8 +61,10 @@ val-form-manager>
    -
    {{vm.createFolderError.errorMsg}}
    -

    {{vm.createFolderError.data.message}}

    +
    +
    {{vm.createFolderError.errorMsg}}
    +
    {{vm.createFolderError.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html index 4ca34379e6..59c0b0b344 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html @@ -51,8 +51,10 @@ val-form-manager>
    -
    {{vm.createFolderError.errorMsg}}
    -

    {{vm.createFolderError.data.message}}

    +
    +
    {{vm.createFolderError.errorMsg}}
    +
    {{vm.createFolderError.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js index 4c8c5f845a..a6ef34f43b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js @@ -1,4 +1,4 @@ -function booleanEditorController($scope, $rootScope, assetsService) { +function booleanEditorController($scope) { function setupViewModel() { $scope.renderModel = { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 443b677790..0f90239b0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,6 +1,10 @@
    + on-click="toggle()" + show-labels="{{model.config.labelOn ? 'true': 'false'}}" + label-position="right" + label-on="{{model.config.labelOn}}" + label-off="{{model.config.labelOn}}">
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index 14c37d2ee4..17be7718d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -11,7 +11,8 @@ function ColorPickerController($scope) { //map back to the model $scope.model.config = config; - + + //TODO: This isn't used function convertArrayToDictionaryArray(model) { //now we need to format the items in the dictionary because we always want to have an array var newItems = []; @@ -22,7 +23,7 @@ function ColorPickerController($scope) { return newItems; } - + //TODO: This isn't used function convertObjectToDictionaryArray(model) { //now we need to format the items in the dictionary because we always want to have an array var newItems = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html index e209d0cb60..c86e7f44f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html @@ -1,5 +1,6 @@ 
    +
    You haven't defined any colors
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index a70ecabf35..1056b45f9d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -3,18 +3,22 @@
    - +
    -
    +
    -
    -
    #{{item.value}}
    {{item.label}}
    +
    +
    +
    #{{item.value}}
    + + +
    Remove diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 7ae728a85a..287a0f48fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -5,8 +5,9 @@ var defaultLabel = null; $scope.newColor = defaultColor; - $scope.newLavel = defaultLabel; + $scope.newLabel = defaultLabel; $scope.hasError = false; + $scope.focusOnNew = false; $scope.labels = {}; @@ -104,7 +105,6 @@ }; $scope.add = function (evt) { - evt.preventDefault(); if ($scope.newColor) { @@ -117,7 +117,9 @@ value: $scope.newColor, label: newLabel }); + $scope.newLabel = ""; $scope.hasError = false; + $scope.focusOnNew = true; return; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index 0fb0e75d09..9bed59b0d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -35,6 +35,22 @@ function dateTimePickerController($scope, notificationsService, assetsService, a } }; + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + if (newVal != oldVal) { + //check for c# System.DateTime.MinValue being passed as the clear indicator + var minDate = moment('0001-01-01'); + var newDate = moment(newVal); + + if (newDate.isAfter(minDate)) { + applyDate({ date: moment(newVal) }); + } else { + $scope.clearDate(); + } + } + }; + //handles the date changing via the date picker function applyDate(e) { angularHelper.safeApply($scope, function() { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index b5b9910465..7d7405bedb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -285,9 +285,11 @@ angular.module("umbraco") event: event, show: true, submit: function(model) { + if (model.selectedItem) { $scope.addControl(model.selectedItem, area, index); $scope.editorOverlay.show = false; $scope.editorOverlay = null; + } } }; }); @@ -888,8 +890,8 @@ angular.module("umbraco") angular.forEach($scope.availableEditors, function (value, key) { //If no translation is provided, keep using the editor name from the manifest if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { - localizationService.localize("grid_" + value.alias).then(function(value){ - value.name = value; + localizationService.localize("grid_" + value.alias).then(function(v){ + value.name = v; }); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.controller.js new file mode 100644 index 0000000000..1402226f78 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.controller.js @@ -0,0 +1,25 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.IdWithGuidValueController + * @function + * + * @description + * The controller for the idwithguid property editor, which formats the ID as normal + * with the GUID in smaller text below, as used across the backoffice. +*/ +function IdWithGuidValueController($rootScope, $scope, $filter) { + + function formatDisplayValue() { + if ($scope.model.value.length > 1) { + $scope.displayid = $scope.model.value[0]; + $scope.displayguid = $scope.model.value[1]; + } else { + $scope.displayid = $scope.model.value; + } + } + + //format the display value on init: + formatDisplayValue(); +} + +angular.module('umbraco').controller("Umbraco.PropertyEditors.IdWithGuidValueController", IdWithGuidValueController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.html new file mode 100644 index 0000000000..02179b7c71 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/idwithguid/idwithguid.html @@ -0,0 +1,4 @@ +
    +
    {{ displayid }}
    + {{ displayguid }} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index 04bfe98284..e4a7bd5267 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -1,15 +1,18 @@ angular.module('umbraco') .controller("Umbraco.PropertyEditors.ImageCropperController", - function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) { + function ($scope, fileManager) { var config = angular.copy($scope.model.config); $scope.filesSelected = onFileSelected; $scope.filesChanged = onFilesChanged; $scope.fileUploaderInit = onFileUploaderInit; + $scope.imageLoaded = imageLoaded; $scope.crop = crop; $scope.done = done; $scope.clear = clear; + $scope.reset = reset; + $scope.close = close; $scope.focalPointChanged = focalPointChanged; //declare a special method which will be called whenever the value has changed from the server $scope.model.onValueChanged = onValueChanged; @@ -65,6 +68,11 @@ angular.module('umbraco') $scope.imageCropperForm.$setDirty(); } + function imageLoaded (isCroppable, hasDimensions) { + $scope.isCroppable = isCroppable; + $scope.hasDimensions = hasDimensions; + }; + /** * Called when the file collection changes * @param {any} value @@ -122,7 +130,8 @@ angular.module('umbraco') * @param {any} crop */ function crop(crop) { - $scope.currentCrop = crop; + // clone the crop so we can discard the changes + $scope.currentCrop = angular.copy(crop); $scope.currentPoint = null; //set form to dirty to track changes @@ -131,13 +140,28 @@ angular.module('umbraco') /** done cropping */ function done() { - $scope.currentCrop = null; - $scope.currentPoint = null; + if (!$scope.currentCrop) { + return; + } + // find the original crop by crop alias and update its coordinates + var editedCrop = _.find($scope.model.value.crops, crop => crop.alias === $scope.currentCrop.alias); + editedCrop.coordinates = $scope.currentCrop.coordinates; + $scope.close(); //set form to dirty to track changes $scope.imageCropperForm.$setDirty(); }; + function reset() { + $scope.currentCrop.coordinates = undefined; + $scope.done(); + } + + function close() { + $scope.currentCrop = undefined; + $scope.currentPoint = undefined; + } + /** * crop a specific crop * @param {any} crop @@ -171,7 +195,7 @@ angular.module('umbraco') }) .run(function (mediaHelper, umbRequestHelper) { if (mediaHelper && mediaHelper.registerFileResolver) { - + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource // they contain different data structures so if we need to query against it we need to be aware of this. mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index 5e920cee01..4f46f4c5ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -2,7 +2,6 @@ ng-controller="Umbraco.PropertyEditors.ImageCropperController"> -
    - -
    - - Reset - +
    + + + +
    +
    + on-value-changed="focalPointChanged(left, top)" + on-image-loaded="imageLoaded(isCroppable, hasDimensions)"> Remove file
    - + -
      +
      • -
        +
        - - + + - -
        No content has been added
        -
        No members have been added
        -
        + +
        No content has been added
        +
        No members have been added
        +
        - - - + + + - - - + + + -
        +
        -
        +
        - - + + - - + + - - + + - -
          -
        • -
          {{ property.header }}
          -
          {{ vm.mediaDetailsTooltip.item[property.alias] }}
          -
        • -
        -
        + +
          +
        • +
          {{ property.header }}
          +
          {{ vm.mediaDetailsTooltip.item[property.alias] }}
          +
        • +
        +
        - - - + + + - - - + + +
        diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index a4b4518fd7..2d411597cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -2,6 +2,9 @@ var backspaceHits = 0; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = "-1"; + $scope.sortableOptions = { axis: 'y', containment: 'parent', @@ -89,6 +92,9 @@ }; $scope.remove = function (index) { + // Make sure not to trigger other prompts when remove is triggered + $scope.hidePrompt(); + var remainder = []; for (var x = 0; x < $scope.model.value.length; x++) { if (x !== index) { @@ -98,6 +104,20 @@ $scope.model.value = remainder; }; + $scope.showPrompt = function (idx, item){ + + var i = $scope.model.value.indexOf(item); + + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + } + + $scope.hidePrompt = function(){ + $scope.promptIsVisible = "-1"; + } + } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 71bf24e735..9f45c46a77 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -5,11 +5,20 @@ - - - + +
        + + + + +
        +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index 7aa2a14b26..c45e118891 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -1,5 +1,5 @@
    -
    Loading...
    +
    Loading...
    - - -
    +
    + + + +
    -
    -
    +
    +
    Last login:
    -
    +
    {{ model.user.formattedLastLogin }} {{ model.user.name | umbWordLimit:1 }} has not logged in yet
    -
    -
    +
    +
    Failed login attempts:
    -
    +
    {{ model.user.failedPasswordAttempts }}
    -
    -
    +
    +
    Last lockout date:
    -
    +
    {{ model.user.name | umbWordLimit:1 }} hasn't been locked out @@ -364,11 +364,11 @@
    -
    -
    +
    +
    Password is last changed:
    -
    +
    The password hasn't been changed @@ -376,24 +376,36 @@
    -
    -
    +
    +
    User is created:
    -
    +
    {{ model.user.formattedCreateDate }}
    -
    -
    +
    +
    User is last updated:
    -
    +
    {{ model.user.formattedUpdateDate }}
    +
    +
    + Id: +
    +
    + {{ model.user.id }} +
    +
    + {{ model.user.key }} +
    +
    + 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 6a567ba5e6..07aa9935a8 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -11,19 +11,19 @@ module.exports = function (config) { files: [ //libraries - 'lib-bower/jquery/jquery.min.js', - 'lib-bower/angular/angular.js', - 'lib-bower/angular-animate/angular-animate.js', - 'lib-bower/angular-cookies/angular-cookies.js', - 'lib-bower/angular-local-storage/angular-local-storage.min.js', - 'lib-bower/angular-route/angular-route.js', - 'lib-bower/angular-sanitize/angular-sanitize.js', - 'bower_components/angular-mocks/angular-mocks.js', - 'lib-bower/angular-ui-sortable/sortable.js', - 'lib-bower/underscore/underscore-min.js', - 'lib-bower/moment/moment-with-locales.js', + 'node_modules/jquery/dist/jquery.min.js', + 'node_modules/angular/angular.js', + 'node_modules/angular-animate/angular-animate.js', + 'node_modules/angular-cookies/angular-cookies.js', + 'node_modules/angular-local-storage/dist/angular-local-storage.min.js', + 'node_modules/angular-route/angular-route.js', + 'node_modules/angular-sanitize/angular-sanitize.js', + 'node_modules/angular-mocks/angular-mocks.js', + 'node_modules/angular-ui-sortable/dist/sortable.js', + 'node_modules/underscore/underscore-min.js', + 'node_modules/moment/min/moment-with-locales.js', 'lib/umbraco/Extensions.js', - 'lib-bower/rgrove-lazyload/lazyload.js', + 'node_modules/lazyload-js/lazyload.min.js', //app bootstrap and loader 'test/config/app.unit.js', diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2fc8cfc8bc..d18dab1987 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -198,13 +198,6 @@ umbracoPage.Master - - treeInit.aspx - ASPXCodeBehind - - - treeInit.aspx - @@ -325,7 +318,6 @@ - @@ -340,7 +332,6 @@ - Designer @@ -352,16 +343,12 @@ - - - - Designer @@ -445,7 +432,6 @@ Form - diff --git a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs index 220b86fa5f..7848a5976b 100644 --- a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Web; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.IO; -using umbraco.cms.presentation.Trees; using Umbraco.Web._Legacy.UI; namespace Umbraco.Web.UI.Umbraco diff --git a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml index 67adeaf84a..1d397bfd01 100644 --- a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml @@ -45,7 +45,7 @@

    Did you know

    -

    +

    {{installer.feedback}}

    @@ -70,7 +70,7 @@ "umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder" }; - + diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs deleted file mode 100644 index f82be2b80f..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.UI.Umbraco -{ - [Obsolete("Used the TreeControl control instead. This does however get used by the TreeService when requesting the tree init url.")] - public partial class TreeInit : Pages.UmbracoEnsuredPage - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); - DataBind(); - } - - protected TreeService TreeParams { get; private set; } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs deleted file mode 100644 index 7009fe3289..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco { - - - public partial class TreeInit { - - /// - /// Head1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlHead Head1; - - /// - /// ClientLoader control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.JavaScript.UmbracoClientDependencyLoader ClientLoader; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - - /// - /// ScriptManager1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.ScriptManager ScriptManager1; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 549954bfc1..3c79d5458c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 83fc836841..3cdf65ffd8 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -124,7 +124,7 @@ } - + @if (isDebug) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 4924985689..fcd375da45 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -32,7 +32,7 @@ @Html.Partial(Model.PreviewExtendedHeaderView) } -
    +
    @@ -54,7 +54,7 @@
    - + diff --git a/src/Umbraco.Web.UI/Umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/Umbraco/config/create/UI.Release.xml index 52abf37133..0ebdb5cd48 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/create/UI.Release.xml @@ -15,50 +15,6 @@ - -
    membergroup
    - /create/simple.ascx - - - -
    - -
    membergroup
    - /create/simple.ascx - - - -
    - -
    Stylesheet
    - /create/simple.ascx - - - -
    - -
    Stylesheet
    - /create/simple.ascx - - - - -
    - -
    Stylesheet editor egenskab
    - /create/simple.ascx - - - - -
    - -
    Stylesheet editor egenskab
    - /create/simple.ascx - - - -
    Package
    /create/simple.ascx diff --git a/src/Umbraco.Web.UI/Umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/Umbraco/config/create/UI.xml index 82adc71525..d6be62ff88 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/create/UI.xml @@ -15,50 +15,6 @@
    - -
    membergroup
    - /create/simple.ascx - - - -
    - -
    membergroup
    - /create/simple.ascx - - - -
    - -
    Stylesheet
    - /create/simple.ascx - - - -
    - -
    Stylesheet
    - /create/simple.ascx - - - - -
    - -
    Stylesheet editor egenskab
    - /create/simple.ascx - - - - -
    - -
    Stylesheet editor egenskab
    - /create/simple.ascx - - - -
    Package
    /create/simple.ascx diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index df94e02566..d2366b46f6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -446,7 +446,7 @@ Stiskněte tlačítko povýšit pro povýšení Vaší databáze na Umbraco %0%

    Neobávejte se - žádný obsah nebude odstraněn a všechno bude fungovat jak má! -

    +

    ]]> Stiskněte Následující pro pokračování. ]]> následující, pro pokračování konfiguračního průvodce]]> @@ -482,7 +482,7 @@ ]]> Chci začít od nuly zjistěte jak) Stále se můžete později rozhodnout nainstalovat Runway. Za tím účelem navštivte Vývojářskou sekci a zvolte Balíčky. ]]> @@ -516,7 +516,7 @@ Abyste získali pomoc od naší oceňované komunity, projděte si dokumentaci, nebo si pusťte některá videa zdarma o tom, jak vytvořit jednoduchý web, jak používat balíčky a rychlý úvod do terminologie umbraca]]> Umbraco %0% je nainstalováno a připraveno k použití soubor /web.config a upravit klíč AppSetting umbracoConfigurationStatus dole na hodnotu '%0%'.]]> - ihned začít kliknutím na tlačítko "Spustit Umbraco" níže.
    Jestliže je pro Vás umbraco nové, + ihned začít kliknutím na tlačítko "Spustit Umbraco" níže.
    Jestliže je pro Vás umbraco nové, spoustu zdrojů naleznete na naších stránkách "začínáme".]]>
    Spustit Umbraco Chcete-li spravovat Váš web, jednoduše přejděte do administrace umbraca a začněte přidávat obsah, upravovat šablony a stylopisy, nebo přidávat nové funkce]]> @@ -537,6 +537,7 @@ Obnovte nyní pro uložení práce + Šťastnou super neděli Šťastné šílené pondělí Šťastné husté úterý Šťastnou překrásnou středu @@ -583,13 +584,13 @@ ]]>
    Hi %0%

    -

    Toto je automatická zpráva informující Vás, že úloha '%1%' +

    Toto je automatická zpráva informující Vás, že úloha '%1%' byla provedena na stránce '%2%' uživatelem '%3%'

    @@ -601,7 +602,7 @@

    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 2f31f2d24e..e624e22b75 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -145,6 +145,7 @@ Brugeren har tilbagerullet indholdet til en tidligere tilstand Brugeren har sendt indholdet til udgivelse Brugeren har sendt indholdet til oversættelse + Brugeren har sorteret de underliggende sider Kopieret Udgivet Flyttet @@ -154,6 +155,7 @@ Indhold tilbagerullet Sendt til udgivelse Sendt til oversættelse + Sorteret For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering. @@ -254,7 +256,7 @@ Klik for at uploade eller klik her for at vælge filer - Du kan trække filer herind for at uploade + Du kan trække filer herind for at uploade. Kan ikke uploade denne fil, den har ikke en godkendt filtype Maks filstørrelse er Medie rod @@ -421,6 +423,8 @@ Indtast din e-mail Indtast en besked... Dit brugernavn er typisk din e-mailadresse + Indtast alias... + Genererer alias... Tilladte typer @@ -530,6 +534,7 @@ Skjul Historik Ikon + Id Importer Indre margen Indsæt @@ -720,6 +725,7 @@ Forny for at gemme dine ændringer + Så er det søndag! Smil, det er mandag! Hurra, det er tirsdag! Hvilken herlig onsdag! @@ -917,6 +923,8 @@ Mange hilsner fra Umbraco robotten Nulstil + Acceptér + Fortryd Nuværende version diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index 6e26dfeec7..b2296b1ccf 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -483,7 +483,7 @@ Umbraco benötigt Schreibrechte auf verschiedene Verzeichnisse, um Dateien wie Bilder oder PDF-Dokumente speichern zu können. Außerdem werden temporäre Daten zur Leistungssteigerung der Website angelegt. Ich möchte mit einem leeren System ohne Inhalte und Vorgaben starten - Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. + Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">So geht's</a>) Sie können "Runway" auch jederzeit später installieren. Verwenden Sie hierzu den Punkt "Pakete" im Entwickler-Bereich. @@ -497,8 +497,8 @@ Dies sind unsere empfohlenen Module. Schauen Sie sich die an, die Sie installier Ich möchte mit einer einfache Website starten <p> -"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, -aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. +"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, +aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. Runway bietet eine einfache Basis zum schnellen Start mit Umbraco. Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die "Runway Modules" und Ihre Runway-Seite erweitern. </p> @@ -535,6 +535,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erneuern Sie, um Ihre Arbeit zu speichern ... + Einen wunderbaren Sonntag Schönen Montag Einen großartigen Dienstag Wunderbaren Mittwoch @@ -578,13 +579,13 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Ihr freundlicher Umbraco-Robot Hallo %0%

    -

    Dies ist eine automatisch E-Mail, welche Sie informiert, dass die Aufgabe '%1%' +

    Dies ist eine automatisch E-Mail, welche Sie informiert, dass die Aufgabe '%1%' an der Seite '%2%' vom Benutzer '%3%' ausgeführt wurde.

    @@ -596,7 +597,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die

    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index cea12955be..6c2d663451 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -148,6 +148,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -157,6 +158,7 @@ Rollback Send To Publish Send To Translation + Sort To change the document type for the selected content, first select from the list of valid types for this location. @@ -282,7 +284,7 @@ Document Type without a template New folder New data type - New javascript file + New JavaScript file New empty partial view New partial view macro New partial view from snippet @@ -440,6 +442,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allowed child node types @@ -557,6 +561,7 @@ Hide History Icon + Id Import Info Inner margin @@ -608,7 +613,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we cannot find what you are looking for No items have been added Server Settings @@ -801,6 +806,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday @@ -1101,7 +1107,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access @@ -1165,11 +1171,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version @@ -1258,6 +1266,7 @@ To manage your website, simply open the Umbraco back office and start adding con Sent For Approval Changes have been sent for approval Media saved + Member group saved Media saved without any errors Member saved Stylesheet Property Saved @@ -1583,6 +1592,9 @@ To manage your website, simply open the Umbraco back office and start adding con Stylesheets Templates Users + Settings + Templating + Third Party New update ready @@ -1930,4 +1942,4 @@ To manage your website, simply open the Umbraco back office and start adding con There is no 'restore' relation found for this node. Use the Move menu item to move it manually. The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. - \ No newline at end of file + 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 ae75d7af43..3c4ca4a74d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -118,6 +118,7 @@ Insert macro Insert picture Publish and close + Publish with descendants Edit relations Return to list Save @@ -152,6 +153,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -161,6 +163,7 @@ Rollback Send To Publish Send To Translation + Sort To change the document type for the selected content, first select from the list of valid types for this location. @@ -225,6 +228,8 @@ Published Published (pending changes)> Publication Status + Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> + Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> Publish at Unpublish at Clear Date @@ -251,6 +256,7 @@ Add another text box Remove this text box Content root + Include drafts: also publish 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? @@ -264,6 +270,7 @@ Ready to Publish? Ready to Save? Send for approval + Select the date and time to publish and/or unpublish the content item. Create a new Content Template from '%0%' @@ -285,6 +292,7 @@ Create a new member All Members + Member groups have no additional properties for editing. Where do you want to create the new %0% @@ -296,7 +304,7 @@ Document Type without a template New folder New data type - New javascript file + New JavaScript file New empty partial view New partial view macro New partial view from snippet @@ -454,6 +462,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allowed child node types @@ -571,6 +581,7 @@ Hide History Icon + Id Import Info Inner margin @@ -622,7 +633,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we cannot find what you are looking for No items have been added Server Settings @@ -815,6 +826,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday @@ -1115,7 +1127,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access @@ -1147,7 +1159,6 @@ To manage your website, simply open the Umbraco back office and start adding con - Include unpublished subpages Publishing in progress - please wait... %0% out of %1% pages have been published... %0% has been published @@ -1179,11 +1190,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version @@ -1272,6 +1285,7 @@ To manage your website, simply open the Umbraco back office and start adding con Media saved Media saved without any errors Member saved + Member group saved Stylesheet Property Saved Stylesheet saved Template saved @@ -1612,6 +1626,9 @@ To manage your website, simply open the Umbraco back office and start adding con Stylesheets Templates Users + Settings + Templating + Third Party New update ready @@ -1966,4 +1983,4 @@ To manage your website, simply open the Umbraco back office and start adding con Select your notifications for Notification settings saved for - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 78fb3b7b2d..bc6de4d7e8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -410,7 +410,7 @@ añadir prevalor - Tipo de datos GUID Tipo de datos GUIDprestar control @@ -421,7 +421,7 @@ Mostrar etiqueta - Todos los tipos y datos de propiedad usar este tipo de datos lo borrará permanentemente, por favor confirma que quieres borrarlos también @@ -696,6 +696,7 @@ Renovar su sesión para guardar sus cambios + Feliz super domingo Feliz lunes Tremendo martes Maravilloso miércoles @@ -714,41 +715,41 @@ El enlace pulsado es inválido o ha caducado Umbraco: Restaurar contraseña - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Restauración de contraseña requerida

    @@ -758,12 +759,12 @@

    - - + + +
    +
    Pulsa este enlace para restaurar tu contraseña - -
    @@ -775,22 +776,22 @@ %1% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> @@ -916,7 +917,7 @@ Define una sección nombrada @section { ... }. Esto se puede mostrar en un área específica de la plantilla madre usando @RenderSection. ]]> Muestra una sección nombrada @@ -1300,13 +1301,13 @@ Campos Incluir subpáginas @@ -1451,41 +1452,41 @@ Volver a usuarios Umbraco: Invitación - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Hi %0%,

    @@ -1503,12 +1504,12 @@
    - - + + +
    +
    Pulsa este enlace para aceptar la invitación - -
    @@ -1523,22 +1524,22 @@ %3% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> @@ -1672,4 +1673,4 @@ caracteres restantes - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 0605474494..598748a4eb 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -89,7 +89,7 @@ Domaine '%0%' mis à jour Editer les domaines actuels
    Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela + "https://www.example.com/".

    Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela devrait être évité. Utilisez plutôt la gestion de la culture et des noms d'hôte.]]>
    Hériter Culture @@ -673,9 +673,9 @@ Appuyez sur le bouton Upgrader pour mettre à jour votre base de données vers Umbraco %0%

    N'ayez pas d'inquiétude : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement par après ! -

    +

    ]]> - Appuyez sur Suivant pour + Appuyez sur Suivant pour poursuivre. ]]> Suivant pour poursuivre la configuration]]> Le mot de passe par défaut doit être modifié !]]> @@ -725,7 +725,7 @@ "Runway" est un site simple qui fournit des types de documents et des modèles de base. L'installateur peut mettre en place Runway automatiquement pour vous, - mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, + mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, Runway offre une base facile, fondée sur des bonnes pratiques, pour vous permettre de commencer plus rapidement que jamais. Si vous choisissez d'installer Runway, vous pouvez sélectionner en option des blocs de base, appelés Runway Modules, pour enrichir les pages de votre site.

    @@ -746,7 +746,7 @@ Vous avez installé Runway, alors pourquoi ne pas jeter un oeil au look de votre Aide et informations complémentaires Obtenez de l'aide de notre communauté "award winning", parcourez la documentation ou regardez quelques vidéos gratuites sur la manière de construire un site simple, d'utiliser les packages ainsi qu'un guide rapide sur la terminologie Umbraco]]> Umbraco %0% est installé et prêt à être utilisé - fichier /web.config et mettre à jour le paramètre AppSetting umbracoConfigurationStatus situé en bas à la valeur '%0%'.]]> démarrer instantanément en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
    Si vous débutez avec Umbraco, vous pouvez trouver une foule de ressources dans nos pages "Getting Started".]]>
    @@ -769,6 +769,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Renouvellez votre session maintenant pour sauvegarder votre travail + Joyeux dimanche détonnant Joyeux lundi lumineux Joyeux mardi magique Joyeux mercredi merveilleux @@ -790,41 +791,41 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Le lien sur lequel vous avez cliqué est non valide ou a expiré. Umbraco: Ré-initialiser le mot de passe - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Une réinitialisation de votre mot de passe a été demandée

    @@ -834,12 +835,12 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

    - - + + +
    +
    Cliquez sur ce lien pour réinitialiser votre mot de passe - -
    @@ -851,22 +852,22 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %1% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> @@ -905,54 +906,54 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Avec les salutations du Robot Umbraco ]]> - - - - - - - + + + + + + + - - + +
    +
    - - + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + -
    +

    - - + - +
    +
    - - +
    +

    Salut %0%,

    -

    +

    Ceci est un email automatique pour vous informer que la tâche '%1%' a été exécutée sur la page '%2%' par l'utilisateur '%3%'

    - - @@ -962,7 +963,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

    Résumé de la mise à jour :

    + +
    MODIFIER
    %6% -
    +

    Bonne journée !

    @@ -974,10 +975,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à

    -


    +


    @@ -1646,41 +1647,41 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Retour aux utilisateurs Umbraco: Invitation - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    +
    + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Salut %0%,

    @@ -1698,12 +1699,12 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
    - - + + +
    +
    Cliquez sur ce lien pour accepter l'invitation - -
    @@ -1718,22 +1719,22 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %3% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> Inviter diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index f2db37592b..0fb00ef139 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -375,7 +375,7 @@ 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 + Click the retry button when done.
    More information on editing web.config here.

    ]]>
    @@ -386,9 +386,9 @@ 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 + Press Next to proceed. ]]> next to continue the configuration wizard]]> The Default users’ password needs to be changed!]]> @@ -423,7 +423,7 @@ ]]> אני רוצה להתחיל מאתר ריק learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -437,8 +437,8 @@ ברצוני להתחיל עם אתר פשוט - "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" 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.

    @@ -459,9 +459,9 @@ 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]]> אומברקו %0% מותקנת ומוכנה לשימוש - /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, + 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]]> @@ -515,13 +515,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
    Hi %0%

    -

    This is an automated mail to inform you that the task '%1%' +

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

    @@ -533,7 +533,7 @@ To manage your website, simply open the Umbraco back office and start adding con

    @@ -801,9 +801,9 @@ To manage your website, simply open the Umbraco back office and start adding con באפשרותך גם להיכנס למערכת על מנת לראות את כל משימות התירגום http://%3% - + המשך יום נעים. - + ]]>
    לא נמצאו משתמשמים המוגדרים כמתרגמים. יש ליצור משתמש המוגדר כמתרגם לפני שליחת תוכן לתירגום העמוד '%0%' נשלח לתירגום diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 427d3421ec..7b8da34c7a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -411,8 +411,8 @@ Vorrei iniziare da un sito semplice - "Runway" è un semplice sito web contenente alcuni tipi di documento e alcuni templates di base. L'installer configurerà Runway per te automaticamente, - ma tu potrai facilmente modificarlo, estenderlo o eliminarlo. Non è necessario installarlo e potrai usare Umbraco anche senza di esso, ma + "Runway" è un semplice sito web contenente alcuni tipi di documento e alcuni templates di base. L'installer configurerà Runway per te automaticamente, + ma tu potrai facilmente modificarlo, estenderlo o eliminarlo. Non è necessario installarlo e potrai usare Umbraco anche senza di esso, ma Runway ti offre le basi e le best practices per cominciare velocemente. Se sceglierai di installare Runway, volendo potrai anche selezionare i moduli di Runway per migliorare le pagine.

    @@ -491,13 +491,13 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i ]]>
    Salve %0%

    -

    Questa è un'email automatica per informare che l'azione '%1%' +

    Questa è un'email automatica per informare che l'azione '%1%' è stata eseguita sulla pagina '%2%' dall'utente '%3%'

    @@ -509,7 +509,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i

    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 4b9bda319e..e53abc2b53 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -615,6 +615,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 + ハッピー スーパー日曜日 ハッピー マニアック月曜日 ハッピー最高の火曜日 ハッピー ワンダフル水曜日 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index 98c94fb222..f2d27094a8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -408,7 +408,7 @@ Umbraco 는 특정 디렉토리에 쓰기/수정 권한이 필요합니다. 이것은 PDF나 그림과 같은 파일을 저장하고 cache같은 임시데이터을 위해 사용됩니다. scratch를 시작하기 원합니다. learn how) Runway설치를 나중에 실행하실 수 있습니다. 개발도구 부분에서 패키지를 선택하세요. ]]> @@ -445,7 +445,7 @@ 우수 커뮤니티에서 도음을 받으세요. 간단한 사이트제작이나 패키지 사용법, Umbraco기술의 퀵가이드를 제공하는 문서를 보시거나 무료 비디오를 시청하세요.]]>
    Umbraco %0% 가 설치되어 사용준비가 되었습니다. /web.config file을 수동으로 편집해야 합니다. AppSetting 키의 UmbracoConfigurationStatus'%0%'의 값으로 설정하세요.]]> - Umbraco 와 첫만남이시면
    아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. + Umbraco 와 첫만남이시면
    아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. 시작페이지에서 풍부한 리소소를 제공받을 수 있습니다.]]>
    Umbraco 실행 사이트 관리를 위해서 Umbraco 관리자를 여시고 컨텐츠를 추가하시거나 템플릿과 스타일시트 업데이트 또는 새기능을 추가하세요]]> @@ -492,21 +492,21 @@ 사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다. - + 편집하시려면 http://%4%/#/content/content/edit/%5% 로 이동하세요 좋은 하루 되세요! - + ]]>
    안녕하세요 %0%

    -

    사용자 '%3%' 가 작업 '%1%' 를 +

    사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다.

    @@ -518,11 +518,11 @@

    -

    좋은 하루 되세요!

    +

    좋은 하루 되세요!

    ]]>
    %1%에 대한 [%0]알림이 %2%에 생성되었습니다 알림 @@ -779,8 +779,8 @@ %2% 에 의해 문서 '%1%' 가 '%5%' 로 번역요청되었음을 알리는 자동 발송 메일입니다. - 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 - + 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 + 번역작업을 전반적으로 보시려면 Umbraco에 로그인 하세요 http://%3% diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 62d6e5419a..98a63a946c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -285,7 +285,7 @@ Aantal regels Plaats een placeholder id door een ID op uw placeholder te zetten kunt u inhoud plaatsen in deze template vanuit onderliggende templates, door te referreren naar deze ID door gebruik te maken van een <asp:content /> element.]]> - Selecteer een placeholder id uit onderstaande lijst. U kunt alleen + Selecteer een placeholder id uit onderstaande lijst. U kunt alleen Id's kiezen van de master van de huidige template..]]> Klik op de afbeelding voor volledige grootte Kies een item @@ -579,7 +579,7 @@ Ik wil met een eenvoudige website beginnen "Runway" is een eenvoudige website die je van enkele elementaire documenttypes en templates voorziet. De installer kan Runway automatisch voor je opzetten, maar je kunt het gemakkelijk aanpassen, uitbreiden of verwijderen. Het is niet vereist en je kunt Umbraco prima zonder Runway gebruiken. -Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren.

    Runway omvat: Home pagina, Getting Started pagina, Module installatie pagina.
    Optionele Modules: Top Navigatie, Sitemap, Contact, Gallery.
    +Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren.

    Runway omvat: Home pagina, Getting Started pagina, Module installatie pagina.
    Optionele Modules: Top Navigatie, Sitemap, Contact, Gallery.
    ]]>
    Wat is Runway Stap 1/5: Licentie aanvaarden @@ -609,6 +609,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Vernieuw je sessie om je wijzigingen te behouden + Goede zondag Fijne maandag Fijne dinsdag Fijne woensdag @@ -662,17 +663,17 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Een prettige dag! Dit is een bericht van uw Content Management Systeem. - + ]]>
    Hi %0%

    -

    Dit is een geautomatiseerde mail om u op de hoogte te brengen dat de taak '%1%' - is uitgevoerd op pagina '%2%' +

    Dit is een geautomatiseerde mail om u op de hoogte te brengen dat de taak '%1%' + is uitgevoerd op pagina '%2%' door gebruiker '%3%'

    @@ -684,7 +685,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je

    @@ -770,7 +771,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %0% kan niet worden gepubliceerd, omdat de eigenschappen:%1% de validatieregels niet hebben doorstaan. ]]>
    Geen vertaal-gebruikers gevonden. Maak eerst een vertaal-gebruiker aan voordat je pagina's voor vertaling verstuurd De pagina '%0%' is verstuurd voor vertaling @@ -1219,41 +1220,41 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Terug naar gebruikers Umbraco: Uitnodiging - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Hallo %0%,

    @@ -1271,12 +1272,12 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
    - - + + +
    +
    Klik hier om de uitnodiging te accepteren - -
    @@ -1291,22 +1292,22 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %3% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> Uitnodigen @@ -1433,4 +1434,4 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Er is geen 'herstel' relatie gevonden voor dit item. Gebruik de "Verplaats" optie om het manueel terug te zetten Het item dat u wil herstellen onder ('%0%') zit in de prullenbak. Gebruik de "Verplaats" optie om het manueel terug te zetten - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 254a01c179..779a3c2a76 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -87,7 +87,7 @@ Domena '%0%' została zaktualizowana Edytuj Aktualne Domeny Odziedziczona Język @@ -335,9 +335,9 @@ Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktualna, lecz nieopublikowana zawartość pozostanie niewidoczna Liczba kolumn Liczba wierszy - Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, + Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]> - Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko + Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko spośród ID na szablonie nadrzędnym tego formularza.]]> Kliknij na obrazie, aby zobaczyć go w pełnym rozmiarze Wybierz element @@ -626,23 +626,23 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umbraco %0% ]]> Dalej, aby kontynuować.]]> - Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

    -

    Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

    + Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

    +

    Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

    - Kliknij ponów próbę kiedy + Kliknij ponów próbę kiedy skończysz.
    Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

    ]]>
    - Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. + Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]> - Naciśnij przycisk aktualizuj, aby zaktualizować swoją bazę danych do Umbraco %0%

    + Naciśnij przycisk aktualizuj, aby zaktualizować swoją bazę danych do Umbraco %0%

    Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy!

    ]]>
    - Naciśnij przycisk Dalej, aby + Naciśnij przycisk Dalej, aby kontynuować.]]> Dalej, aby kontynuować kreatora instalacji.]]> Hasło domyślnego użytkownika musi zostać zmienione!]]> @@ -655,7 +655,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Zmienione pliki i foldery Więcej informacji na temat ustalania pozwoleń dla Umbraco znajdziesz tutaj Musisz zezwolić procesowi ASP.NET na zmianę poniższych plików/folderów - Twoje ustawienia uprawnień są prawie idealne!

    + Twoje ustawienia uprawnień są prawie idealne!

    Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
    Jak to Rozwiązać Kliknij tutaj, aby przeczytać wersję tekstową @@ -663,42 +663,42 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Twoje ustawienia uprawnień mogą stanowić problem!

    Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
    - Twoje ustawienia uprawnień nie są gotowe na Umbraco! -

    + Twoje ustawienia uprawnień nie są gotowe na Umbraco! +

    Aby Umbraco mogło działać musisz uaktualnić swoje ustawienia zabezpieczeń.]]>
    - Twoje ustawienia uprawnień są idealne!

    + Twoje ustawienia uprawnień są idealne!

    Umbraco będzie działać bez problemów i będzie można instalować pakiety!]]>
    Rozwiązywanie problemów z folderami Kliknij ten link, aby uzyskać więcej informacji na temat problemów z ASP.NET i tworzeniem folderów. Ustawianie uprawnień dostępu do folderów Chcę zacząć od zera dowiedz się jak) + Twoja strona jest obecnie pusta. To idealna sytuacja, jeśli chcesz zacząć od zera i stworzyć własne typy dokumentów i szablony. + (dowiedz się jak) Ciągle możesz wybrać, aby zainstalować Runway w późniejszym terminie. W tym celu przejdź do sekcji Deweloper i wybierz Pakiety. ]]> Właśnie stworzyłeś czystą instalację platformy Umbraco. Co chcesz zrobić teraz? Pakiet Runway został zainstalowany pomyślnie - To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów + Twoje fundamenty są postawione właściwie. Wybierz, które moduły chcesz na nich zainstalować.
    + To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów ]]>
    Rekomendowane tylko dla doświadczonych użytkowników Chcę rozpocząć z prostą stroną - Pakiet "Runway" to prosta strona, dostarczająca kilku podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, - ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. - Jednakże pakiet Runway oferuje łatwą podstawę, bazującą na najlepszych praktykach, która pozwolić Ci rozpocząć pracę w mgnieniu oka. - Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. -

    + Pakiet "Runway" to prosta strona, dostarczająca kilku podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, + ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. + Jednakże pakiet Runway oferuje łatwą podstawę, bazującą na najlepszych praktykach, która pozwolić Ci rozpocząć pracę w mgnieniu oka. + Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. +

    - Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
    - Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria. + Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
    + Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria.
    ]]>
    Co to jest pakiet Runway @@ -708,23 +708,23 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Krok 4/5: Sprawdzanie zabezpieczeń Umbraco Krok 5/5: Umbraco jest gotowe do pracy Dziękujemy za wybór Umbraco - Przeglądaj swoją nową stronę + Przeglądaj swoją nową stronę Pakiet Runway został zainstalowany, zobacz zatem jak wygląda Twoja nowa strona.]]> - Dalsza pomoc i informacje + Dalsza pomoc i informacje Zaczerpnij pomocy z naszej nagrodzonej społeczności, przeglądaj dokumentację lub obejrzyj niektóre darmowe filmy o tym, jak budować proste strony, jak używać pakietów i szybki przewodnik po terminologii Umbraco]]> Umbraco %0% zostało zainstalowane i jest gotowe do użycia - plik web.config i zaktualizować klucz AppSetting o nazwie UmbracoConfigurationStatus na dole do wartości '%0%'.]]> - rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
    Jeżeli jesteś nowy dla Umbraco + rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
    Jeżeli jesteś nowy dla Umbraco znajdziesz mnóstwo materiałów na naszych stronach "jak rozpocząć".]]>
    - Uruchom Umbraco + Uruchom Umbraco Aby zarządzać swoją stroną po prostu otwórz zaplecze Umbraco i zacznij dodawać treść, aktualizować szablony i style lub dodawaj nową funkcjonalność]]> Połączenie z bazą danych nie zostało ustanowione. Umbraco wersja 3 Umbraco wersja 4 Zobacz - Umbraco %0% dla świeżej instalacji lub aktualizacji z wersji 3.0. -

    + Umbraco %0% dla świeżej instalacji lub aktualizacji z wersji 3.0. +

    Wciśnij "dalej", aby rozpocząć proces konfigruacji.]]>
    @@ -736,6 +736,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Wznów sesję teraz, aby zapisać swoją pracę + Szczęśliwej super niedzieli Szczęśliwego maniakalnego poniedziałku Szczęśliwego świetnego wtorku Szczęśliwej niesamowitej środy @@ -778,11 +779,11 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Edytuj powiadomienie dla %0% instaluj, aby zainstalować bazę danych Umb Pozdrowienia od robota Umbraco ]]> - Witaj %0%

    + Witaj %0%

    -

    To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' - zostało wykonane na stronie '%2%' - przez użytkownika '%3%' -

    -
    +

    To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' + zostało wykonane na stronie '%2%' + przez użytkownika '%3%' +

    + -

    -

    Podsumowanie zmian:

    - - %6% -
    -

    +      EDYTUJ        +
    +
    +

    +

    Podsumowanie zmian:

    + + %6% +
    +

    - + -

    Miłego dnia!

    - Pozdrowienia od robota Umbraco +

    Miłego dnia!

    + Pozdrowienia od robota Umbraco

    ]]>
    [%0%] Powiadomienie o %1% wykonane na %2% Powiadomienie + Wskaż pakiet z Twojego komputera, poprzez kliknięcie na przycisk "Przeglądaj"
    i wskaż gdzie jest zapisany. Pakiety Umbraco przeważnie posiadają rozszerzenie ".umb" lub ".zip". ]]>
    Upuść, aby załadować @@ -875,7 +876,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Pakiet został pomyślnie odinstalowany Odinstaluj pakiet - Uwaga: wszystkie elementy, media, itp. w zależności od elementów, które usuwasz, przestaną działać i mogą spowodować niestabilność systemu, + Uwaga: wszystkie elementy, media, itp. w zależności od elementów, które usuwasz, przestaną działać i mogą spowodować niestabilność systemu, więc odinstalowuj z uwagą. W przypadku problemów skontaktuj się z autorem pakietu.]]> Pobierz aktualizację z repozytorium Aktualizuj pakiet @@ -941,7 +942,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb %0% został opublikowany %0% oraz podstrony zostały opublikowane Publikuj %0% ze wszytkimi podstronami - OK , aby publikować % 0% i spowodować upublicznienie całej zawartości.

    + OK , aby publikować % 0% i spowodować upublicznienie całej zawartości.

    Możesz opublikować tą stronę wraz ze wszystkimi podstronami zaznaczając poniżej publikuj wszystkie węzły pochodne ]]>
    @@ -1283,15 +1284,15 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index f613ae1055..98f557a78c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -373,8 +373,8 @@ Pressione o botão atualizar para atualizar seu banco de dados para Umbraco %0%

    - Não se preocupe - nenhum conteúdo será removido e tudo estará funcionando depois disto!

    - + Não se preocupe - nenhum conteúdo será removido e tudo estará funcionando depois disto!

    + ]]>
    Pressione Próximo para prosseguir.]]> próximo
    para continuar com o assistente de configuração]]>
    @@ -403,7 +403,7 @@ Para correr Umbraco você vai precisar atualizar as configurações de permissõ Resolvendo problemas de pastas Siga este link para mais informações sobre problemas com ASP.NET e criação de pastas Configurando permissões de pastas - Eu quero começar do zero Incluso com Runway: Página Inicial, Começando, Instalando Módulos.
    Módulos Opcionais: Navegação de Topo, Mapa de Site, Contato, Galeria. - + ]]>
    O que é Runway Passo 1/5 Aceitar Licença @@ -491,13 +491,13 @@ Vá até http://%4%/#/content/content/edit/%5% para editar. Saudações do robô Umbraco]]>
    Olá %0%

    -

    Esta é uma mensagem automatizada para informar que a tarefa '%1%' +

    Esta é uma mensagem automatizada para informar que a tarefa '%1%' foi completada na página '%2%' pelo usuário '%3%'

    @@ -509,7 +509,7 @@ Vá até http://%4%/#/content/content/edit/%5% para editar.

    diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 97fe7c61a8..942f77e85b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -912,6 +912,7 @@ © 2001 - %0%
    umbraco.com

    ]]>
    + Сегодня же выходной! Понедельник — день тяжелый... Вот уже вторник... Берегите окружающую среду @@ -930,41 +931,41 @@ Ссылка, по которой Вы попали сюда, неверна или устарела Umbraco: сброс пароля - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Запрошен сброс пароля

    @@ -974,12 +975,12 @@

    - - + + +
    +
    Нажмите на эту ссылку для того, чтобы сбросить пароль - -
    @@ -991,22 +992,22 @@ %1% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> @@ -1068,53 +1069,53 @@ Генератор уведомлений Umbraco. ]]> - - - - - - + + + + + + + - - + +
    +
    - - + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + -
    +

    - - + - +
    +
    - - +
    +

    Здравствуйте, %0%,

    -

    +

    Это автоматически сгенерированное сообщение, отправленное, чтобы уведомить Вас о том, что операция '%1%' была выполнена на странице '%2%' пользователем '%3%'

    - - @@ -1124,7 +1125,7 @@

    Обзор обновления:

    + +
    ВНЕСТИ ИЗМЕНЕНИЯ
    %6% -
    +

    Удачного дня!

    @@ -1136,10 +1137,10 @@

    -


    +


    @@ -1703,41 +1704,41 @@ Пригласить Приглашение в панель администрирования Umbraco

    Здравствуйте, %0%,

    Вы были приглашены пользователем %1%, и Вам предоставлен доступ в панель администрирования Umbraco.

    Сообщение от %1%: %2%

    Перейдите по этой ссылке, чтобы принять приглашение.

    Если Вы не имеете возможности перейти по ссылке, скопируйте нижеследующий текст ссылки и вставьте в адресную строку Вашего браузера.

    %3%

    ]]> - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    +
    + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Здравствуйте, %0%,

    @@ -1755,12 +1756,12 @@
    - - + + +
    +
    Нажмите на эту ссылку, чтобы принять приглашение - -
    @@ -1775,22 +1776,22 @@ %3% -

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index f16129b315..7a3380996c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -502,6 +502,7 @@ © 2001 - %0%
    umbraco.com

    ]]>
    + Happy super Sunday Happy manic Monday Happy tremendous Tuesday Happy wonderful Wednesday diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 26c6079f7c..79304d86c2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -55,7 +55,7 @@ etki /> bir düzey yollar desteklenir
    Miras Al Kültür @@ -421,7 +421,7 @@ Veritabanı bulunamadı! "Web.config" nin "bağlantı dizesinde" bilgi dosyası doğru olup olmadığını kontrol edin.

    Devam etmek için, (Visual Studio veya sevdiğiniz metin editörü kullanarak) "web.config" dosyasını düzenlemek lütfen, altına gidin "UmbracoDbDSN" adlı anahtarı veritabanınız için bağlantı dizesini eklemek ve dosyayı kaydedin.

    - Tekrar dene. + Tekrar dene.
    Burada düzenleme web.config Hakkında Daha Fazla Bilgi.

    ]]>
    @@ -434,7 +434,7 @@

    Merak etmeyin - hiçbir içerik silinmeyecek ve her şey sonradan çalışmaya devam edecektir! -

    +

    ]]> Sonraki işlem. ]]> next
    to continue the configuration wizard]]> @@ -470,7 +470,7 @@ ]]> Baştan başlamak istiyorum learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -484,8 +484,8 @@ 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" 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.

    @@ -506,9 +506,9 @@ 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, + 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]]> @@ -529,6 +529,7 @@ To manage your website, simply open the Umbraco back office and start adding con İşinizi kaydetmek için şimdi Yenile + Pazar Pazartesi Salı Çarşamba @@ -575,13 +576,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
    Hi %0%

    -

    This is an automated mail to inform you that the task '%1%' +

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

    @@ -593,7 +594,7 @@ To manage your website, simply open the Umbraco back office and start adding con

    @@ -986,4 +987,4 @@ To manage your website, simply open the Umbraco back office and start adding con Son tarih Oturum sona eriyor - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index c44672a0f9..1dbadcff4c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -60,7 +60,7 @@ 只需要设置语言部分即可。]]>
    继承 语言 - + 也可以从父节点继承。]]> 域名 @@ -543,7 +543,7 @@ 点击更新来更新系统到 %0%

    不用担心更新会丢失数据! -

    +

    ]]>
    点击下一步继续。]]> 下一步继续]]> @@ -615,7 +615,7 @@ 更多的帮助信息 从社区获取帮助]]> 系统 %0% 安装完毕 - /web.config file 的 AppSetting 键 + /web.config file 的 AppSetting 键 UmbracoConfigurationStatus'%0%'。]]> 立即开始请点“运行系统”
    如果您是新手, 您可以得到相当丰富的学习资源。]]>
    运行系统 @@ -638,6 +638,7 @@ 已更新,继续工作。 + 星期一快乐 星期二快乐 星期三快乐 星期四快乐 @@ -700,7 +701,7 @@

    @@ -712,7 +713,7 @@

    @@ -1073,7 +1074,7 @@ %0%: 您好!这是一封自动邮件来提醒您注意,%2%的文档'%1%' - 需要您翻译为'%5%' + 需要您翻译为'%5%' 转到 http://%3%/translation/details.aspx?id=%4% 进行编辑 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index e9c3fa6460..9a94020275 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -299,7 +299,7 @@ %0%' 編輯不同語言版本,
    您可以在左方選單「語言」中增添新的語言 + 為此字典項目 '%0%' 編輯不同語言版本,
    您可以在左方選單「語言」中增添新的語言 ]]>
    語言名稱 @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 @@ -671,7 +672,7 @@ 這是一封自動產生的信件來通知您 %1% 工作 已經在頁面 %2% 上由使用者 %3% 執行完成 - + 請移至下列網址編輯: http://%4%/#/content/content/edit/%5% @@ -1056,7 +1057,7 @@ 哈嘍 %0% 這是一封自動產生的郵件通知您文檔 %1% - 已經被 %2 要求翻譯成 %5% + 已經被 %2 要求翻譯成 %5% 請移至此網址來編輯: http://%3%/translation/details.aspx?id=%4% diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs deleted file mode 100644 index a15ebbf2c7..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration.Provider; -using System.Linq; -using System.Web; -using System.Web.Security; - -namespace Umbraco.Web.UI.Umbraco.Controls -{ - public partial class PasswordChanger : global::umbraco.controls.passwordChanger - { - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //always reset the control vals - ResetPasswordCheckBox.Checked = false; - umbPasswordChanger_passwordCurrent.Text = null; - umbPasswordChanger_passwordNew.Text = null; - umbPasswordChanger_passwordNewConfirm.Text = null; - //reset the flag always - IsChangingPasswordField.Value = "false"; - this.DataBind(); - } - - - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs deleted file mode 100644 index 94193a1450..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Controls { - - - public partial class PasswordChanger { - - /// - /// ResetPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder ResetPlaceHolder; - - /// - /// CurrentPasswordPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder CurrentPasswordPlaceHolder; - - /// - /// CurrentPasswordValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator CurrentPasswordValidator; - - /// - /// NewPasswordRequiredValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator NewPasswordRequiredValidator; - - /// - /// NewPasswordLengthValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RegularExpressionValidator NewPasswordLengthValidator; - - /// - /// Div1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl Div1; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx deleted file mode 100644 index 2ddab055f6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService language="C#" class="umbraco.controls.Tree.CustomTreeService" %> \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx deleted file mode 100644 index 9488fb0643..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx +++ /dev/null @@ -1,57 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="true" Inherits="umbraco.controls.Tree.TreeControl" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="Umbraco.Web" %> - - - - - - - - - - - -
    -
    -
    -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx b/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx deleted file mode 100644 index 9f94a70c45..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx +++ /dev/null @@ -1,133 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="True" CodeBehind="passwordChanger.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Controls.PasswordChanger" %> - - - -<%= Services.TextService.Localize("user/changePassword") %>
    - - - -
    -

    - Password has been reset to
    -
    - <%# ChangingPasswordModel.GeneratedPassword %> -

    -
    diff --git a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs index b9c0577c95..96433be9cc 100644 --- a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Web.UI; using System.Web.UI.WebControls; @@ -9,12 +8,9 @@ using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using umbraco.cms.presentation.Trees; using System.Linq; -using Umbraco.Web.UI; using Umbraco.Web.UI.Pages; using Umbraco.Core.Services; -using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web._Legacy.Controls; diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx deleted file mode 100644 index e67b5b0b32..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx +++ /dev/null @@ -1,67 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" - AutoEventWireup="True" Inherits="umbraco.dialogs.create" %> - -<%@ Import Namespace="Umbraco.Web" %> -<%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - " /> - - - - - - -
    - " onclick="onNodeSelectionConfirmed();" - disabled="true" style="width: 100px" /> -   - <%= Services.TextService.Localize("or") %>  - <%=Services.TextService.Localize("cancel")%> -
    -
    - - - - - -
    diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm b/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm deleted file mode 100644 index 4df7696ed6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm +++ /dev/null @@ -1,9 +0,0 @@ - - - - Umbraco - empty document - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx index 7e76240d14..5bcf97310f 100644 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx +++ b/src/Umbraco.Web.UI/Umbraco/dialogs/protectPage.aspx @@ -74,15 +74,19 @@ - - + +
    + +
    + +
    -
    +
    @@ -116,74 +120,72 @@ + +
    + - - -
    - - - - -
    - -
    - - - -
    - - -

    Member name already exists, click Change to use a different name or Update to continue

    -
    -
    - - - -

    <%= Services.TextService.Localize("publicAccess/paSelectRoles")%>

    -
    - - - -
    - - - - - - - <%=Services.TextService.Localize("paLoginPageHelp")%> - -
    - +
    + + + +
    - - - - - - - <%=Services.TextService.Localize("paErrorPageHelp")%> - -
    - +
    + + +
    - - - + +

    Member name already exists, click Change to use a different name or Update to continue

    +
    + + + + +

    <%= Services.TextService.Localize("publicAccess/paSelectRoles")%>

    +
    + + + +
    + + + + + + <%=Services.TextService.Localize("paLoginPageHelp")%> + +
    + +
    + + +
    + + + <%=Services.TextService.Localize("paErrorPageHelp")%> + +
    + +
    + +
    + +
    +
    - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 84b2cc5cb8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,98 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - -
    - - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
    -
    -

    - -

    -
    - - - -
    -
    -
    -
    - - -
    diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx deleted file mode 100644 index 9add7d07f2..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx +++ /dev/null @@ -1,22 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" CodeBehind="treePicker.aspx.cs" - AutoEventWireup="True" Inherits="umbraco.dialogs.treePicker" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umb2" TagName="Tree" Src="../controls/Tree/TreeControl.ascx" %> - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js b/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js index f0d041d37b..f738538f90 100644 --- a/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js +++ b/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js @@ -8,21 +8,21 @@ function UmbracoSpeechBubble(id) { this.GenerateSpeechBubble(); } -UmbracoSpeechBubble.prototype.GenerateSpeechBubble = function() { +UmbracoSpeechBubble.prototype.GenerateSpeechBubble = function () { var sbHtml = document.getElementById(this.id); sbHtml.innerHTML = '' + - '
    ' + - '
    ' + - '' + - ' Close' + - '

    The header!

    ' + - '

    Default Text Container!


    ' + - '
    ' + - '
    ' -} + '
    ' + + '
    ' + + '' + + ' Close' + + '

    The header!

    ' + + '

    Default Text Container!


    ' + + '
    ' + + '
    '; +}; UmbracoSpeechBubble.prototype.ShowMessage = function (icon, header, message, dontAutoHide) { var speechBubble = jQuery("#" + this.id); @@ -46,7 +46,7 @@ UmbracoSpeechBubble.prototype.ShowMessage = function (icon, header, message, don jQuery(".speechClose").show(); } } -} +}; UmbracoSpeechBubble.prototype.Hide = function () { if (!this.ie) { @@ -54,7 +54,7 @@ UmbracoSpeechBubble.prototype.Hide = function () { } else { jQuery("#" + this.id).hide(); } -} +}; // Initialize var UmbSpeechBubble = null @@ -65,4 +65,4 @@ function InitUmbracoSpeechBubble() { jQuery(document).ready(function() { InitUmbracoSpeechBubble(); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx b/src/Umbraco.Web.UI/Umbraco/treeInit.aspx deleted file mode 100644 index aca6ecc07a..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx +++ /dev/null @@ -1,47 +0,0 @@ -<%@ Page Language="c#" CodeBehind="TreeInit.aspx.cs" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.TreeInit" %> -<%@ Register Src="controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.JavaScript" Assembly="Umbraco.Web" %> - - - - - - - - - - - - - - - - - -
    - - -
    - -
    -
    - - diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config index 32a165399c..442493e3d6 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config @@ -124,7 +124,7 @@ - + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.config index ac8c5cc901..c466b14f1c 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.config @@ -124,7 +124,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index e9c3fa6460..9a94020275 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -299,7 +299,7 @@ %0%' 編輯不同語言版本,
    您可以在左方選單「語言」中增添新的語言 + 為此字典項目 '%0%' 編輯不同語言版本,
    您可以在左方選單「語言」中增添新的語言 ]]>
    語言名稱 @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 @@ -671,7 +672,7 @@ 這是一封自動產生的信件來通知您 %1% 工作 已經在頁面 %2% 上由使用者 %3% 執行完成 - + 請移至下列網址編輯: http://%4%/#/content/content/edit/%5% @@ -1056,7 +1057,7 @@ 哈嘍 %0% 這是一封自動產生的郵件通知您文檔 %1% - 已經被 %2 要求翻譯成 %5% + 已經被 %2 要求翻譯成 %5% 請移至此網址來編輯: http://%3%/translation/details.aspx?id=%4% diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 844855f10f..50319370e9 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,9 +82,14 @@ + diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 4e22907218..a4e2e63059 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -7,6 +7,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.ContentApps; // the namespace here is intentional - although defined in Umbraco.Web assembly, // this class should be visible when using Umbraco.Core.Components, alongside @@ -30,6 +31,14 @@ namespace Umbraco.Core.Components internal static ActionCollectionBuilder Actions(this Composition composition) => composition.Container.GetInstance(); + /// + /// Gets the content apps collection builder. + /// + /// The composition. + /// + public static ContentAppDefinitionCollectionBuilder ContentApps(this Composition composition) + => composition.Container.GetInstance(); + /// /// Gets the content finders collection builder. /// diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index 7dda00e62c..559c3e3fb6 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -18,9 +19,20 @@ namespace Umbraco.Web.ContentApps _logger = logger; } - public IEnumerable GetContentAppsFor(object o) + private IEnumerable GetCurrentUserGroups() { - var apps = this.Select(x => x.GetContentAppFor(o)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + var umbracoContext = Composing.Current.UmbracoContext; + var currentUser = umbracoContext?.Security?.CurrentUser; + return currentUser == null + ? Enumerable.Empty() + : currentUser.Groups; + + } + + public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) + { + var roles = GetCurrentUserGroups(); + var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); var aliases = new HashSet(); List dups = null; diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs index c2d6341e87..d54d1a44d4 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs index be7a40f007..de490439ba 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 5c73b2fa8c..0e4c7a04b8 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -22,9 +23,10 @@ namespace Umbraco.Web.ContentApps _propertyEditors = propertyEditors; } - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { string contentTypeAlias, entityType; + int dtdId; switch (o) { @@ -33,22 +35,33 @@ namespace Umbraco.Web.ContentApps case IContent content: contentTypeAlias = content.ContentType.Alias; entityType = "content"; + dtdId = Core.Constants.DataTypes.DefaultContentListView; break; case IMedia media when !media.ContentType.IsContainer && media.ContentType.Alias != Core.Constants.Conventions.MediaTypes.Folder: return null; case IMedia media: contentTypeAlias = media.ContentType.Alias; entityType = "media"; + dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias); + return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); } - public static ContentApp CreateContentApp(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors, string entityType, string contentTypeAlias) + public static ContentApp CreateContentApp(IDataTypeService dataTypeService, + PropertyEditorCollection propertyEditors, + string entityType, string contentTypeAlias, + int defaultListViewDataType) { + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (propertyEditors == null) throw new ArgumentNullException(nameof(propertyEditors)); + if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentException("message", nameof(entityType)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("message", nameof(contentTypeAlias)); + if (defaultListViewDataType == default) throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); + var contentApp = new ContentApp { Alias = "umbListView", @@ -59,10 +72,10 @@ namespace Umbraco.Web.ContentApps }; var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - var dtdId = Core.Constants.DataTypes.DefaultContentListView; + //first try to get the custom one if there is one var dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(dtdId); + ?? dataTypeService.GetDataType(defaultListViewDataType); if (dt == null) { diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 1b32d86e6f..ae74009415 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -524,38 +524,6 @@ namespace Umbraco.Web.Editors return true; } - /// - /// Returns the JavaScript blocks for any legacy trees declared - /// - /// - [UmbracoAuthorize(Order = 0)] - [MinifyJavaScriptResult(Order = 1)] - public JavaScriptResult LegacyTreeJs() - { - Func getResult = () => - { - var javascript = new StringBuilder(); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); - //add all of the menu blocks - foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) - { - javascript.AppendLine(file); - } - return javascript.ToString(); - }; - - //cache the result if debugging is disabled - var result = HttpContext.IsDebuggingEnabled - ? getResult() - : ApplicationCache.RuntimeCache.GetCacheItem( - typeof(BackOfficeController) + "LegacyTreeJs", - () => getResult(), - new TimeSpan(0, 10, 0)); - - return JavaScript(result); - } - internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values) { var blockList = new List(); @@ -586,7 +554,7 @@ namespace Umbraco.Web.Editors } /// - /// Renders out all JavaScript references that have bee declared in IActions + /// Renders out all JavaScript references that have been declared in IActions /// private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) { diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 8eb1c4247f..98114f0664 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Editors var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -112,7 +112,6 @@ namespace Umbraco.Web.Editors {"externalLoginsUrl", _urlHelper.Action("ExternalLogin", "BackOffice")}, {"externalLinkLoginsUrl", _urlHelper.Action("LinkLogin", "BackOffice")}, - {"legacyTreeJs", _urlHelper.Action("LegacyTreeJs", "BackOffice")}, {"manifestAssetList", _urlHelper.Action("GetManifestAssetList", "BackOffice")}, {"gridConfig", _urlHelper.Action("GetGridConfig", "BackOffice")}, //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address @@ -225,6 +224,10 @@ namespace Umbraco.Web.Editors "memberTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetAllTypes()) }, + { + "memberGroupApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAllGroups()) + }, { "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetCheck()) @@ -327,6 +330,7 @@ namespace Umbraco.Web.Editors {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, + {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, } }, { diff --git a/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs new file mode 100644 index 0000000000..eb4d482b14 --- /dev/null +++ b/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs @@ -0,0 +1,24 @@ +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors.Binders +{ + internal class BlueprintItemBinder : ContentItemBinder + { + public BlueprintItemBinder() + { + } + + public BlueprintItemBinder(ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, services, umbracoContextAccessor) + { + } + + protected override IContent GetExisting(ContentItemSave model) + { + return Services.ContentService.GetBlueprintById(model.Id); + } + } +} diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index a3cb1d229a..fd8951993b 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -1,23 +1,15 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; -using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; using Umbraco.Core.Services; using Umbraco.Web.Composing; -using Umbraco.Web.Editors.Filters; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors.Binders { @@ -26,7 +18,6 @@ namespace Umbraco.Web.Editors.Binders ///
    internal class ContentItemBinder : IModelBinder { - private readonly ServiceContext _services; private readonly ContentModelBinderHelper _modelBinderHelper; public ContentItemBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor) @@ -35,10 +26,12 @@ namespace Umbraco.Web.Editors.Binders public ContentItemBinder(ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor) { - _services = services; + Services = services; _modelBinderHelper = new ContentModelBinderHelper(); } + protected ServiceContext Services { get; } + /// /// Creates the model from the request and binds it to the context /// @@ -78,14 +71,14 @@ namespace Umbraco.Web.Editors.Binders return true; } - private IContent GetExisting(ContentItemSave model) + protected virtual IContent GetExisting(ContentItemSave model) { - return _services.ContentService.GetById(model.Id); + return Services.ContentService.GetById(model.Id); } private IContent CreateNew(ContentItemSave model) { - var contentType = _services.ContentTypeService.Get(model.ContentTypeAlias); + var contentType = Services.ContentTypeService.Get(model.ContentTypeAlias); if (contentType == null) { throw new InvalidOperationException("No content type found with alias " + model.ContentTypeAlias); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1fcee6d727..6e095e8393 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -232,7 +232,7 @@ namespace Umbraco.Web.Editors public ContentItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new ContentItemDisplay { @@ -555,7 +555,7 @@ namespace Umbraco.Web.Editors /// [FileUploadCleanupFilter] [ContentSaveValidation] - public ContentItemDisplay PostSaveBlueprint([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + public ContentItemDisplay PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => @@ -1068,7 +1068,7 @@ namespace Umbraco.Web.Editors var contentService = Services.ContentService; // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder) == false) + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) { Logger.Warn("Content sorting failed, this was probably caused by an event being cancelled"); return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); @@ -1702,5 +1702,93 @@ namespace Umbraco.Web.Editors Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions); } + [HttpGet] + public IEnumerable GetRollbackVersions(int contentId, string culture = null) + { + var rollbackVersions = new List(); + var writerIds = new HashSet(); + + //Return a list of all versions of a specific content node + // fixme - cap at 50 versions for now? + var versions = Services.ContentService.GetVersionsSlim(contentId, 0, 50); + + //Not all nodes are variants & thus culture can be null + if (culture != null) + { + //Get cultures that were published with the version = their update date is equal to the version's + versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); + } + + //First item is our current item/state (cant rollback to ourselves) + versions = versions.Skip(1); + + foreach (var version in versions) + { + var rollbackVersion = new RollbackVersion + { + VersionId = version.VersionId, + VersionDate = version.UpdateDate, + VersionAuthorId = version.WriterId + }; + + rollbackVersions.Add(rollbackVersion); + + writerIds.Add(version.WriterId); + } + + var users = Services.UserService + .GetUsersById(writerIds.ToArray()) + .ToDictionary(x => x.Id, x => x.Name); + + foreach (var rollbackVersion in rollbackVersions) + { + if (users.TryGetValue(rollbackVersion.VersionAuthorId, out var userName)) + rollbackVersion.VersionAuthorName = userName; + } + + return rollbackVersions; + } + + [HttpGet] + public ContentVariantDisplay GetRollbackVersion(int versionId, string culture = null) + { + var version = Services.ContentService.GetVersion(versionId); + var content = MapToDisplay(version); + + return culture == null + ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + } + + [HttpPost] + public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") + { + var rollbackResult = Services.ContentService.Rollback(contentId, versionId, culture, Security.GetUserId().ResultOr(0)); + + if (rollbackResult.Success) + return Request.CreateResponse(HttpStatusCode.OK); + + var notificationModel = new SimpleNotificationModel(); + + switch (rollbackResult.Result) + { + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; + } + + return Request.CreateValidationErrorResponse(notificationModel); + } } } diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index fa1aaf7345..22d86631ca 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors throw new ArgumentOutOfRangeException("The entity type was not a content type"); } - var contentTypesWhereCompositionIsUsed = Services.ContentTypeService.GetWhereCompositionIsUsedInContentTypes(source, allContentTypes); + var contentTypesWhereCompositionIsUsed = source.GetWhereCompositionIsUsedInContentTypes(allContentTypes); return contentTypesWhereCompositionIsUsed .Select(x => Mapper.Map(x)) .Select(x => diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 635cdfaa17..dc744ea361 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -99,7 +99,7 @@ namespace Umbraco.Web.Editors public MediaItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView)); apps[0].Active = true; var display = new MediaItemDisplay { @@ -913,4 +913,4 @@ namespace Umbraco.Web.Editors return hasPathAccess; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 9f70c3c33b..6117db8857 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors var name = foundType != null ? foundType.Name : listName; var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new MemberListDisplay diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6111a931e3..94465feab8 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using System.Web.Http; using System.Xml; using umbraco.cms.businesslogic.packager; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -249,7 +248,6 @@ namespace Umbraco.Web.Editors // trigger the UninstalledPackage event PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs(summary, false)); - TreeDefinitionCollection.Instance.ReRegisterTrees(); } /// @@ -587,9 +585,6 @@ namespace Umbraco.Web.Editors var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. - //these bits are super old, but cant find another way to do this currently - global::umbraco.cms.presentation.Trees.TreeDefinitionCollection.Instance.ReRegisterTrees(); var redirectUrl = ""; if (ins.Control.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index d9578d4877..16af20132a 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -5,6 +5,7 @@ using System.Linq; using Umbraco.Core.Models; using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; +using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Editors { @@ -43,16 +44,35 @@ namespace Umbraco.Web.Editors if (hasDashboards == false) { //get the first tree in the section and get it's root node route path - var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - section.RoutePath = sectionTrees.IsContainer == false || sectionTrees.Children.Count == 0 - ? sectionTrees.RoutePath - : sectionTrees.Children[0].RoutePath; + var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; + section.RoutePath = GetRoutePathForFirstTree(sectionRoot); } } return sectionModels; } + /// + /// Returns the first non root/group node's route path + /// + /// + /// + private string GetRoutePathForFirstTree(TreeRootNode rootNode) + { + if (!rootNode.IsContainer || !rootNode.ContainsTrees) + return rootNode.RoutePath; + + foreach(var node in rootNode.Children) + { + if (node is TreeRootNode groupRoot) + return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group + else + return node.RoutePath; + } + + return string.Empty; + } + /// /// Returns all the sections that the user has access to /// diff --git a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/DatabaseSchemaValidationHealthCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/DatabaseSchemaValidationHealthCheck.cs deleted file mode 100644 index d3c60d4b59..0000000000 --- a/src/Umbraco.Web/HealthCheck/Checks/DataIntegrity/DatabaseSchemaValidationHealthCheck.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Services; - -namespace Umbraco.Web.HealthCheck.Checks.DataIntegrity -{ - /// - /// U4-9544 Health check to detect if the database has any missing indexes or constraints - /// - [HealthCheck( - "0873D589-2064-4EA3-A152-C43417FE00A4", - "Database Schema Validation", - Description = "This checks the Umbraco database by doing a comparison of current indexes and schema items with the current state of the database and returns any problems it found. Useful to detect if the database hasn't been upgraded correctly.", - Group = "Data Integrity")] - public class DatabaseSchemaValidationHealthCheck : HealthCheck - { - private readonly DatabaseBuilder _databaseBuilder; - private readonly ILocalizedTextService _textService; - private readonly ILogger _logger; - - public DatabaseSchemaValidationHealthCheck(DatabaseBuilder databaseBuilder, ILocalizedTextService textService, ILogger logger) - { - _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); - _textService = textService ?? throw new ArgumentNullException(nameof(textService)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - return CheckDatabase(); - } - - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckDatabase() }; - } - - private HealthCheckStatus CheckDatabase() - { - var results = _databaseBuilder.ValidateDatabaseSchema(); - - _logger.Warn(_textService.Localize("databaseSchemaValidationCheckDatabaseLogMessage")); - - foreach(var error in results.Errors) - { - _logger.Warn("{Error} : {ErrorDetail}", error.Item1, error.Item2); - } - - if(results.Errors.Count > 0) - { - return new HealthCheckStatus(_textService.Localize("healthcheck/databaseSchemaValidationCheckDatabaseErrors", new[] { results.Errors.Count.ToString() })) - { - ResultType = StatusResultType.Error, - View = "Umbraco.Dashboard.DatabaseSchemaValidationController" - }; - } - - return new HealthCheckStatus(_textService.Localize("healthcheck/databaseSchemaValidationCheckDatabaseOk")) - { - ResultType = StatusResultType.Success - }; - } - } -} diff --git a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs index 33ceffe616..7d47139721 100644 --- a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs @@ -19,13 +19,12 @@ namespace Umbraco.Web.Install.InstallSteps { get { - var currentVersion = UmbracoVersion.Local; - - //fixme - in this case there's a db but the version is cleared which is fine and a normal way to force the upgrader - // to execute, but before we would detect the current version via the DB like DatabaseSchemaResult.DetermineInstalledVersion - // what now, do we need to? - if (currentVersion == null) - currentVersion = new Semver.SemVersion(0); + // fixme - if UmbracoVersion.Local is null? + // it means that there is a database but the web.config version is cleared + // that was a "normal" way to force the upgrader to execute, and we would detect the current + // version via the DB like DatabaseSchemaResult.DetermineInstalledVersion - magic, do we really + // need this now? + var currentVersion = (UmbracoVersion.Local ?? new Semver.SemVersion(0)).ToString(); var newVersion = UmbracoVersion.SemanticVersion.ToString(); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs b/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs index 9f0750cb7c..45c6313d91 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentDomainsAndCulture.cs @@ -1,11 +1,15 @@ using System.Collections.Generic; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "ContentDomainsAndCulture")] public class ContentDomainsAndCulture { + [DataMember(Name = "domains")] public IEnumerable Domains { get; set; } + [DataMember(Name = "language")] public string Language { get; internal set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DomainDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DomainDisplay.cs index a6f7e499e4..ea3ea509c9 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DomainDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DomainDisplay.cs @@ -1,16 +1,26 @@ -namespace Umbraco.Web.Models.ContentEditing +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "DomainDisplay")] public class DomainDisplay - { + { public DomainDisplay(string name, int lang) { Name = name; Lang = lang; } + [DataMember(Name = "name")] public string Name { get; } + + [DataMember(Name = "lang")] public int Lang { get; } + + [DataMember(Name = "duplicate")] public bool Duplicate { get; set; } + + [DataMember(Name = "other")] public string Other { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DomainSave.cs b/src/Umbraco.Web/Models/ContentEditing/DomainSave.cs index 3ad19cfd60..6853762af3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DomainSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DomainSave.cs @@ -1,10 +1,20 @@ -namespace Umbraco.Web.Models.ContentEditing +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "DomainSave")] public class DomainSave { + [DataMember(Name = "valid")] public bool Valid { get; set; } + + [DataMember(Name = "nodeId")] public int NodeId { get; set; } + + [DataMember(Name = "language")] public int Language { get; set; } + + [DataMember(Name = "domains")] public DomainDisplay[] Domains { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs new file mode 100644 index 0000000000..dad1341a8c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "rollbackVersion", Namespace = "")] + public class RollbackVersion + { + [DataMember(Name = "versionId")] + public int VersionId { get; set; } + + [DataMember(Name = "versionDate")] + public DateTime VersionDate { get; set; } + + [DataMember(Name = "versionAuthorId")] + public int VersionAuthorId { get; set; } + + [DataMember(Name = "versionAuthorName")] + public string VersionAuthorName { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/Tab.cs b/src/Umbraco.Web/Models/ContentEditing/Tab.cs index c83f293f08..758317606c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Tab.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Tab.cs @@ -21,6 +21,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "alias")] public string Alias { get; set; } + /// + /// The expanded state of the tab + /// + [DataMember(Name = "open")] + public bool Expanded { get; set; } = true; + [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Web/Models/ImageCropMode.cs b/src/Umbraco.Web/Models/ImageCropMode.cs index e36f079c02..03fe6676cb 100644 --- a/src/Umbraco.Web/Models/ImageCropMode.cs +++ b/src/Umbraco.Web/Models/ImageCropMode.cs @@ -2,11 +2,34 @@ namespace Umbraco.Web.Models { public enum ImageCropMode { + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is cropped to match the new aspect ratio. + /// Crop, + + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is resized to the maximum possible value in each direction while aintaining the original aspect ratio. + /// Max, + + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is stretched to match the new aspect ratio. + /// Stretch, + + /// + /// Passing a single dimension will automatically preserve the aspect ratio of the original image. If the requested aspect ratio is different then the image will be padded to fit. + /// Pad, + + /// + /// When upscaling an image the image pixels themselves are not resized, rather the image is padded to fit the given dimensions. + /// BoxPad, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. This will maintain the aspect ratio of the original image. Upscaling is disabled in this mode and the original image will be returned if attempted. + /// Min } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs index b249760053..0b086afb2d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs @@ -24,7 +24,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) .ForMember(tab => tab.Properties, expression => expression.Ignore()) - .ForMember(tab => tab.Alias, expression => expression.Ignore()); + .ForMember(tab => tab.Alias, expression => expression.Ignore()) + .ForMember(tab => tab.Expanded, expression => expression.Ignore()); //FROM Property TO ContentPropertyBasic CreateMap().ConvertUsing(contentPropertyBasicConverter); diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs index f64d8dc529..3fb603cb32 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Models.Mapping //FROM MembershipUser TO IMember - used when using a non-umbraco membership provider CreateMap() .ConstructUsing(src => MemberService.CreateGenericMembershipProviderMember(src.UserName, src.Email, src.UserName, "")) - //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity + //we're giving this entity an ID of int.MaxValue - TODO: SD: I can't remember why this mapping is here? .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.MaxValue)) .ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src.Comment)) .ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate)) @@ -84,6 +84,8 @@ namespace Umbraco.Web.Models.Mapping //FROM IMember TO MemberBasic CreateMap() + //we're giving this entity an ID of int.MaxValue - this is kind of a hack to force angular to use the Key instead of the Id in list views + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.MaxValue)) .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Member, content.Key))) .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => memberOwnerResolver.Resolve(src))) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) @@ -99,7 +101,7 @@ namespace Umbraco.Web.Models.Mapping //FROM MembershipUser TO MemberBasic CreateMap() - //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity + //we're giving this entity an ID of int.MaxValue - TODO: SD: I can't remember why this mapping is here? .ForMember(dest => dest.Id, opt => opt.MapFrom(src => int.MaxValue)) .ForMember(dest => dest.Udi, opt => opt.Ignore()) .ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate)) @@ -142,8 +144,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Icon, opt => opt.Ignore()) .ForMember(dest => dest.Trashed, opt => opt.Ignore()) .ForMember(dest => dest.ParentId, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.Path, opt => opt.Ignore()); + .ForMember(dest => dest.Alias, opt => opt.Ignore()); } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs index 9f6747ea95..0e4b902196 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs @@ -71,7 +71,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier // if we just had all of the membeship provider fields on the member table :( @@ -112,11 +112,18 @@ namespace Umbraco.Web.Models.Mapping protected override IEnumerable GetCustomGenericProperties(IContentBase content) { - var member = (IMember) content; + var member = (IMember)content; var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); var genericProperties = new List { + new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id", + Label = _localizedTextService.Localize("general/id"), + Value = new List {member.Id.ToString(), member.Key.ToString()}, + View = "idwithguid" + }, new ContentPropertyDisplay { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype", diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index a854bbe777..88d772b939 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -201,32 +201,7 @@ namespace Umbraco.Web.Models.Trees } } } - - internal void ConvertLegacyFileSystemMenuItem(string path, string nodeType, string currentSection) - { - // try to get a URL/title from the legacy action, - // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. - var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, - path, - nodeType, - path, currentSection); - if (attempt) - { - var action = attempt.Result; - LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + path + "' ?"); - } - } - } + #endregion } diff --git a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs index 05796015e9..e1fd2218e0 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs @@ -9,6 +9,8 @@ namespace Umbraco.Web.Models.Trees [DataContract(Name = "menuItems", Namespace = "")] public class MenuItemCollection { + public static MenuItemCollection Empty => new MenuItemCollection(); + private readonly MenuItemList _menuItems = new MenuItemList(); public MenuItemCollection() diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 34f8e2d351..cd4fc3e483 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -1,55 +1,155 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core; namespace Umbraco.Web.Models.Trees { /// - /// A special tree node that represents the section root node for any section. + /// A tree node that represents various types of root nodes /// /// + /// + /// A represents: + /// * The root node for a section containing a single tree + /// * The root node for a section containing multiple sub-trees + /// * The root node for a section containing groups of multiple sub-trees + /// * The group node in a section containing groups of multiple sub-trees + /// + /// /// This is required to return the tree data for a given section. Some sections may only contain one tree which means it's section /// root should also display a menu, whereas other sections have multiple trees and the section root shouldn't display a menu. - /// - /// The section root also contains an explicit collection of children. + /// + /// + /// The root node also contains an explicit collection of children. + /// /// [DataContract(Name = "node", Namespace = "")] - public sealed class SectionRootNode : TreeNode + public sealed class TreeRootNode : TreeNode { - public static SectionRootNode CreateMultiTreeSectionRoot(string nodeId, TreeNodeCollection children) - { - var sectionRoot = new SectionRootNode(nodeId, "", "") - { - IsContainer = true, - Children = children - }; + private static readonly string RootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture); + private bool _isGroup; - //some metadata as to whether or not this section contains any trees - sectionRoot.AdditionalData["containsTrees"] = children.Any(); + /// + /// Creates a group node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupNode(TreeNodeCollection children, string section) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsGroup = true, + Children = children, + RoutePath = section + }; return sectionRoot; } - public static SectionRootNode CreateSingleTreeSectionRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + /// + /// Creates a section root node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupedMultiTreeRoot(TreeNodeCollection children) { - return new SectionRootNode(nodeId, getChildNodesUrl, menuUrl) + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children, + ContainsGroups = true + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for non-grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateMultiTreeRoot(TreeNodeCollection children) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for a section with a single tree + /// + /// + /// + /// + /// + /// + /// + public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + { + return new TreeRootNode(nodeId, getChildNodesUrl, menuUrl) { Children = children, Name = title }; } - private SectionRootNode(string nodeId, string getChildNodesUrl, string menuUrl) + /// + /// Private constructor + /// + /// + /// + /// + private TreeRootNode(string nodeId, string getChildNodesUrl, string menuUrl) : base(nodeId, null, getChildNodesUrl, menuUrl) { //default to false IsContainer = false; } + /// + /// Will be true if this is a multi-tree section root node (i.e. contains other trees) + /// [DataMember(Name = "isContainer")] public bool IsContainer { get; private set; } + /// + /// True if this is a group root node + /// + [DataMember(Name = "isGroup")] + public bool IsGroup + { + get => _isGroup; + private set + { + //if a group is true then it is also a container + _isGroup = value; + IsContainer = true; + } + } + + /// + /// True if this root node contains group root nodes + /// + [DataMember(Name = "containsGroups")] + public bool ContainsGroups { get; private set; } + + /// + /// The node's children collection + /// [DataMember(Name = "children")] public TreeNodeCollection Children { get; private set; } + + /// + /// Returns true if there are any children + /// + /// + /// This is used in the UI to configure a full screen section/app + /// + [DataMember(Name = "containsTrees")] + public bool ContainsTrees => Children.Count > 0; } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs index 39403f144a..48e9b46dbe 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs @@ -6,5 +6,15 @@ namespace Umbraco.Web.Models.Trees [CollectionDataContract(Name = "nodes", Namespace = "")] public sealed class TreeNodeCollection : List { + public static TreeNodeCollection Empty => new TreeNodeCollection(); + + public TreeNodeCollection() + { + } + + public TreeNodeCollection(IEnumerable nodes) + : base(nodes) + { + } } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs index 9f8ca9b84d..819d74f8e7 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeExtensions.cs @@ -15,7 +15,7 @@ } /// - /// Sets the node style to show that it is currently protected publicly + /// Sets the node style to show that it is a container type /// /// public static void SetContainerStyle(this TreeNode treeNode) diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs index cbd4e69a9e..a4d894c551 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerConfigurationEditor.cs @@ -105,7 +105,11 @@ namespace Umbraco.Web.PropertyEditors // handle useLabel if (editorValues.TryGetValue("useLabel", out var useLabelObj)) - output.UseLabel = useLabelObj.TryConvertTo(); + { + var convertBool = useLabelObj.TryConvertTo(); + if (convertBool.Success) + output.UseLabel = convertBool.Result; + } // auto-assigning our ids, get next id from existing values var nextId = 1; @@ -125,13 +129,8 @@ namespace Umbraco.Web.PropertyEditors var id = item.Property("id")?.Value?.Value() ?? 0; if (id >= nextId) nextId = id + 1; - // if using a label, replace color by json blob - // (a pity we have to serialize here!) - if (output.UseLabel) - { - var label = item.Property("label")?.Value?.Value(); - value = JsonConvert.SerializeObject(new { value, label }); - } + var label = item.Property("label")?.Value?.Value(); + value = JsonConvert.SerializeObject(new { value, label }); output.Items.Add(new ValueListConfiguration.ValueListItem { Id = id, Value = value }); } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs index 5821823576..dbdedebd0a 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownFlexibleConfigurationEditor.cs @@ -27,7 +27,13 @@ namespace Umbraco.Web.PropertyEditors // handle multiple if (editorValues.TryGetValue("multiple", out var multipleObj)) - output.Multiple = multipleObj.TryConvertTo(); + { + var convertBool = multipleObj.TryConvertTo(); + if (convertBool.Success) + { + output.Multiple = convertBool.Result; + } + } // auto-assigning our ids, get next id from existing values var nextId = 1; @@ -56,7 +62,10 @@ namespace Umbraco.Web.PropertyEditors public override Dictionary ToConfigurationEditor(DropDownFlexibleConfiguration configuration) { - var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => x.Value) ?? new object(); + // map to what the editor expects + var i = 1; + var items = configuration?.Items.ToDictionary(x => x.Id.ToString(), x => new { value = x.Value, sortOrder = i++ }) ?? new object(); + var multiple = configuration?.Multiple ?? false; return new Dictionary diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs index 89c3dbf0e0..d55d3327e1 100644 --- a/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressPropertyEditor.cs @@ -5,7 +5,7 @@ using Umbraco.Core.PropertyEditors.Validators; namespace Umbraco.Web.PropertyEditors { - [DataEditor(Constants.PropertyEditors.Aliases.EmailAddress, "Email address", "email", Icon="icon-message")] + [DataEditor(Constants.PropertyEditors.Aliases.EmailAddress, EditorType.PropertyValue | EditorType.MacroParameter, "Email address", "email", Icon="icon-message")] public class EmailAddressPropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs b/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs index 2dfbf9b0b6..6d962c42f1 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalseConfiguration.cs @@ -9,5 +9,8 @@ namespace Umbraco.Web.PropertyEditors { [ConfigurationField("default", "Default Value", "boolean")] public string Default { get; set; } // fixme - well, true or false?! + + [ConfigurationField("labelOn", "Write a label text", "textstring")] + public string Label { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index be2bbb66c1..ccd3bee744 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -19,5 +19,6 @@ namespace Umbraco.Web.PropertyEditors /// protected override IConfigurationEditor CreateConfigurationEditor() => new TrueFalseConfigurationEditor(); + } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 8786753e4f..a9903669b9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -198,6 +198,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void InitializeRepositoryEvents() { + //fixme: The reason these events are in the repository is for legacy, the events should exist at the service + // level now since we can fire these events within the transaction... so move the events to service level + // plug repository event handlers // these trigger within the transaction to ensure consistency // and are used to maintain the central, database-level XML cache @@ -212,9 +215,9 @@ namespace Umbraco.Web.PublishedCache.NuCache MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; // plug - ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; } private void TearDownRepositoryEvents() @@ -229,9 +232,9 @@ namespace Umbraco.Web.PublishedCache.NuCache //MemberRepository.RemovedVersion -= OnMemberRemovedVersion; MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; - ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; } public override void Dispose() diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 4266edecf1..3c143a6066 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -34,6 +34,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly ICacheProvider _cacheProvider; // at snapshot/request level (see PublishedContentCache) private readonly PublishedContentTypeCache _contentTypeCache; + private readonly object _initializeLock = new object(); + private bool _nodeInitialized; private bool _parentInitialized; private bool _childrenInitialized; @@ -66,15 +68,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); - if (_childrenInitialized == false) InitializeChildren(); + EnsureNodeInitialized(andChildren: true); return _children; } } public override IPublishedProperty GetProperty(string alias) { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); IPublishedProperty property; return _properties.TryGetValue(alias, out property) ? property : null; } @@ -85,8 +86,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); - if (_parentInitialized == false) InitializeParent(); + EnsureNodeInitialized(andParent: true); return _parent; } } @@ -95,7 +95,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _id; } } @@ -104,7 +104,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _key; } } @@ -113,7 +113,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _template; } } @@ -122,7 +122,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _sortOrder; } } @@ -131,7 +131,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _name; } } @@ -144,7 +144,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _writerName; } } @@ -153,7 +153,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _creatorName; } } @@ -162,7 +162,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _writerId; } } @@ -171,7 +171,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _creatorId; } } @@ -180,7 +180,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _path; } } @@ -189,7 +189,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _createDate; } } @@ -198,7 +198,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _updateDate; } } @@ -207,7 +207,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _urlName; } } @@ -216,7 +216,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _level; } } @@ -225,7 +225,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _isDraft; } } @@ -234,7 +234,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _properties.Values; } } @@ -243,7 +243,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_nodeInitialized == false) InitializeNode(); + EnsureNodeInitialized(); return _contentType; } } @@ -256,10 +256,27 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (parent.Attributes?.GetNamedItem("isDoc") != null) _parent = Get(parent, _isPreviewing, _cacheProvider, _contentTypeCache); - // warn: this is not thread-safe... _parentInitialized = true; } + private void EnsureNodeInitialized(bool andChildren = false, bool andParent = false) + { + // In *theory* XmlPublishedContent are a per-request thing, and so should not + // end up being involved into multi-threaded situations - however, it's been + // reported that some users ended up seeing 100% CPU due to infinite loops in + // the properties dictionary in InitializeNode, which would indicate that the + // dictionary *is* indeed involved in some multi-threaded operation. No idea + // what users are doing that cause this, but let's be friendly and use a true + // lock around initialization. + + lock (_initializeLock) + { + if (_nodeInitialized == false) InitializeNode(); + if (andChildren && _childrenInitialized == false) InitializeChildren(); + if (andParent && _parentInitialized == false) InitializeParent(); + } + } + private void InitializeNode() { InitializeNode(this, _xmlNode, _isPreviewing, @@ -268,10 +285,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache out _createDate, out _updateDate, out _level, out _isDraft, out _contentType, out _properties, _contentTypeCache.Get); - // warn: this is not thread-safe... _nodeInitialized = true; } + // internal for some benchmarks internal static void InitializeNode(XmlPublishedContent node, XmlNode xmlNode, bool isPreviewing, out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName, out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path, @@ -398,7 +415,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache .OrderBy(x => x.SortOrder) .ToList(); - // warn: this is not thread-safe _childrenInitialized = true; } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 5fa89e3f7b..5b816c2f26 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -194,9 +194,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; // plug - ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; _withRepositoryEvents = true; } @@ -213,9 +213,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache MemberRepository.ScopeVersionRemove -= OnMemberRemovingVersion; MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; - ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; _withRepositoryEvents = false; } diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index bbf3ee1283..4979a59d45 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -93,7 +93,7 @@ namespace Umbraco.Web.Routing } if (string.IsNullOrWhiteSpace(v)) return false; v = "," + v.Replace(" ", "") + ","; - return v.Contains(a1) || v.Contains(a2); + return v.InvariantContains(a1) || v.InvariantContains(a2); } // fixme - even with Linq, what happens below has to be horribly slow diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs index abd9f08a53..f13c0547b6 100644 --- a/src/Umbraco.Web/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -24,11 +24,13 @@ namespace Umbraco.Web.Services internal const string TreeConfigFileName = "trees.config"; private static string _treeConfig; private static readonly object Locker = new object(); + private readonly Lazy>> _groupedTrees; public ApplicationTreeService(ILogger logger, CacheHelper cache) { _logger = logger; _cache = cache; + _groupedTrees = new Lazy>>(InitGroupedTrees); } /// @@ -281,6 +283,46 @@ namespace Umbraco.Web.Services return list.OrderBy(x => x.SortOrder).ToArray(); } + public IDictionary> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized) + { + var result = new Dictionary>(); + var foundTrees = GetApplicationTrees(applicationAlias, onlyInitialized); + foreach(var treeGroup in _groupedTrees.Value) + { + List resultGroup = null; + foreach(var tree in foundTrees) + { + foreach(var treeAliasInGroup in treeGroup) + { + if (tree.Alias == treeAliasInGroup) + { + if (resultGroup == null) resultGroup = new List(); + resultGroup.Add(tree); + } + } + } + if (resultGroup != null) + result[treeGroup.Key ?? string.Empty] = resultGroup; //key cannot be null so make empty string + } + return result; + } + + /// + /// Creates a group of all tree groups and their tree aliases + /// + /// + /// + /// Used to initialize the field + /// + private IReadOnlyCollection> InitGroupedTrees() + { + var result = GetAll() + .Select(x => (treeAlias: x.Alias, treeGroup: x.GetRuntimeType().GetCustomAttribute(false)?.TreeGroup)) + .GroupBy(x => x.treeGroup, x => x.treeAlias) + .ToList(); + return result; + } + /// /// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the /// callback with the xml document and saves the structure back to disk if saveAfterCallback is true. diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 9c13429b44..91548e44f1 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -1,6 +1,4 @@ -using HtmlAgilityPack; -using System; -using System.Runtime.CompilerServices; +using System; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -38,11 +36,6 @@ namespace Umbraco.Web.Templates { if (urlProvider == null) throw new ArgumentNullException("urlProvider"); - if(string.IsNullOrEmpty(text)) - { - return text; - } - // Parse internal links var tags = LocalLinkPattern.Matches(text); foreach (Match tag in tags) @@ -71,11 +64,6 @@ namespace Umbraco.Web.Templates } } - if (UmbracoConfig.For.UmbracoSettings().Content.StripUdiAttributes) - { - text = StripUdiDataAttributes(text); - } - return text; } @@ -87,9 +75,6 @@ namespace Umbraco.Web.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private static readonly Regex UdiDataAttributePattern = new Regex("data-udi=\"[^\\\"]*\"", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. /// @@ -133,21 +118,5 @@ namespace Umbraco.Web.Templates { return text.CleanForXss(ignoreFromClean); } - - /// - /// Strips data-udi attributes from rich text - /// - /// A html string - /// A string stripped from the data-uid attributes - public static string StripUdiDataAttributes(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - - - return UdiDataAttributePattern.Replace(input, string.Empty); - } } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index d489421353..273a7afb37 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -8,6 +9,7 @@ using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -19,7 +21,7 @@ namespace Umbraco.Web.Trees [AngularJsonOnlyConfiguration] [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController - { + { /// /// Returns the tree nodes for an application /// @@ -29,22 +31,21 @@ namespace Umbraco.Web.Trees /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) + public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { application = application.CleanForXss(); if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); - var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - //find all tree definitions that have the current application alias - var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray(); + var groupedTrees = Services.ApplicationTreeService.GetGroupedApplicationTrees(application, onlyInitialized); + var allTrees = groupedTrees.Values.SelectMany(x => x).ToList(); - if (string.IsNullOrEmpty(tree) == false || appTrees.Length <= 1) + if (string.IsNullOrEmpty(tree) == false || allTrees.Count <= 1) { - var apptree = string.IsNullOrEmpty(tree) == false - ? appTrees.SingleOrDefault(x => x.Alias == tree) - : appTrees.SingleOrDefault(); + var apptree = !tree.IsNullOrWhiteSpace() + ? allTrees.FirstOrDefault(x => x.Alias == tree) + : allTrees.FirstOrDefault(); if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -54,26 +55,71 @@ namespace Umbraco.Web.Trees queryStrings, application); - //this will be null if it cannot convert to ta single root section + //this will be null if it cannot convert to a single root section if (result != null) - return result; - } - - var collection = new TreeNodeCollection(); - foreach (var apptree in appTrees) - { - //return the root nodes for each tree in the app - var rootNode = await GetRootForMultipleAppTree(apptree, queryStrings); - //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode) - if (rootNode != null) { - collection.Add(rootNode); + return result; } } - var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); - multiTree.Name = Services.TextService.Localize("sections/"+ application); - return multiTree; + //Don't apply fancy grouping logic futher down, if we only have one group of items + var hasGroups = groupedTrees.Count > 1; + if (!hasGroups) + { + var collection = new TreeNodeCollection(); + foreach (var apptree in allTrees) + { + //return the root nodes for each tree in the app + var rootNode = await GetRootForMultipleAppTree(apptree, queryStrings); + //This could be null if the tree decides not to return it's root (i.e. the member type tree does this when not in umbraco membership mode) + if (rootNode != null) + { + collection.Add(rootNode); + } + } + + var multiTree = TreeRootNode.CreateMultiTreeRoot(collection); + multiTree.Name = Services.TextService.Localize("sections/" + application); + + return multiTree; + } + + var rootNodeGroups = new List(); + + //Group trees by [CoreTree] attribute with a TreeGroup property + foreach (var treeSectionGroup in groupedTrees) + { + var treeGroupName = treeSectionGroup.Key; + + var groupNodeCollection = new TreeNodeCollection(); + foreach (var appTree in treeSectionGroup.Value) + { + var rootNode = await GetRootForMultipleAppTree(appTree, queryStrings); + if (rootNode != null) + { + //Add to a new list/collection + groupNodeCollection.Add(rootNode); + } + } + + //If treeGroupName == null then its third party + if (treeGroupName.IsNullOrWhiteSpace()) + { + //This is used for the localisation key + //treeHeaders/thirdPartyGroup + treeGroupName = "thirdPartyGroup"; + } + + if (groupNodeCollection.Count > 0) + { + var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection, application); + groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); + + rootNodeGroups.Add(groupRoot); + } + } + + return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(rootNodeGroups.OrderBy(x => x.Name))); } /// @@ -100,12 +146,6 @@ namespace Umbraco.Web.Trees return null; } - var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - return legacyAttempt.Result; - } - throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); } @@ -117,7 +157,7 @@ namespace Umbraco.Web.Trees /// /// /// - private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) + private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) { var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); if (configTree == null) throw new ArgumentNullException(nameof(configTree)); @@ -131,26 +171,16 @@ namespace Umbraco.Web.Trees throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias); } - //if the root node has a route path, we cannot create a single root section because by specifying the route path this would - //override the dashboard route and that means there can be no dashboard for that section which is a breaking change. - if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false - && rootNode.Result.RoutePath != "#" - && rootNode.Result.RoutePath != application) - { - //null indicates this cannot be converted - return null; - } - - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( rootId, rootNode.Result.ChildNodesUrl, rootNode.Result.MenuUrl, rootNode.Result.Name, byControllerAttempt.Result); - //This can't be done currently because the root will default to routing to a dashboard and if we disable dashboards for a section - //that is really considered a breaking change. See above. - //sectionRoot.RoutePath = rootNode.Result.RoutePath; + //assign the route path based on the root node, this means it will route there when the section is navigated to + //and no dashboards will be available for this section + sectionRoot.RoutePath = rootNode.Result.RoutePath; foreach (var d in rootNode.Result.AdditionalData) { @@ -159,23 +189,6 @@ namespace Umbraco.Web.Trees return sectionRoot; } - var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( - rootId, - "", //TODO: I think we'll need this in this situation! - Url.GetUmbracoApiService("GetMenu", rootId) - + "&parentId=" + rootId - + "&treeType=" + application - + "§ion=" + application, - "", //TODO: I think we'll need this in this situation! - legacyAttempt.Result); - - - sectionRoot.AdditionalData.Add("treeAlias", configTree.Alias); - return sectionRoot; - } throw new ApplicationException("Could not render a tree for type " + configTree.Alias); } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index af9eb094d0..171601a338 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -13,12 +13,10 @@ using Umbraco.Core; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Core.Services; using Current = Umbraco.Web.Composing.Current; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; -using UrlHelper = System.Web.Http.Routing.UrlHelper; namespace Umbraco.Web.Trees { @@ -191,125 +189,6 @@ namespace Umbraco.Web.Trees return Attempt.Succeed(instance.GetNodes(id, formCollection)); } - internal static Attempt TryGetRootNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xmlTreeNodeAttempt = TryGetRootXmlNodeFromLegacyTree(appTree, formCollection, urlHelper); - if (xmlTreeNodeAttempt.Success == false) - { - return Attempt.Fail(xmlTreeNodeAttempt.Exception); - } - - //the root can potentially be null, in that case we'll just return a null success which means it won't be included - if (xmlTreeNodeAttempt.Result == null) - { - return Attempt.Succeed(null); - } - - //var temp = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); - var temp = new TreeControllerBaseStuffForLegacy(appTree.Alias, xmlTreeNodeAttempt.Result.Text, urlHelper); - var newRoot = temp.GetRootNode(formCollection); - - return Attempt.Succeed(newRoot); - - } - - internal static Attempt TryGetRootXmlNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - bTree.SetTreeParameters(treeParams); - - var xmlRoot = bTree.RootNode; - - return Attempt.Succeed(xmlRoot); - } - - internal static Attempt TryGetLegacyTreeDef(this ApplicationTree appTree) - { - //This is how the legacy trees worked.... - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - return treeDef == null - ? Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Alias)) - : Attempt.Succeed(treeDef); - } - - internal static Attempt TryLoadFromLegacyTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xTreeAttempt = appTree.TryGetXmlTree(id, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - return Attempt.Succeed(LegacyTreeDataConverter.ConvertFromLegacy(id, xTreeAttempt.Result, urlHelper, currentSection, formCollection)); - } - - internal static Attempt TryGetMenuFromLegacyTreeRootNode(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var rootAttempt = appTree.TryGetRootXmlNodeFromLegacyTree(formCollection, urlHelper); - if (rootAttempt.Success == false) - { - return Attempt.Fail(rootAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); - return Attempt.Succeed(result); - } - - internal static Attempt TryGetMenuFromLegacyTreeNode(this ApplicationTree appTree, string parentId, string nodeId, FormDataCollection formCollection, UrlHelper urlHelper) - { - var xTreeAttempt = appTree.TryGetXmlTree(parentId, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(nodeId, xTreeAttempt.Result, currentSection); - if (result == null) - { - return Attempt.Fail(new ApplicationException("Could not find the node with id " + nodeId + " in the collection of nodes contained with parent id " + parentId)); - } - return Attempt.Succeed(result); - } - - private static Attempt TryGetXmlTree(this ApplicationTree appTree, string id, FormDataCollection formCollection) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - //This is how the legacy trees worked.... - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - - //we currently only support an integer id or a string id, we'll refactor how this works - //later but we'll get this working first - int startId; - if (int.TryParse(id, out startId)) - { - treeParams.StartNodeID = startId; - } - else - { - treeParams.NodeKey = id; - } - var xTree = new XmlTree(); - bTree.SetTreeParameters(treeParams); - bTree.Render(ref xTree); - return Attempt.Succeed(xTree); - } - } } diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 5cb63d7409..5b45888dca 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoApplicationAuthorize(Constants.Applications.Content)] [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 12)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentBlueprintTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 3a990a7741..d622bc1436 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -92,6 +92,7 @@ namespace Umbraco.Web.Trees } } + node.AdditionalData.Add("variesByCulture", documentEntity.Variations.VariesByCulture()); node.AdditionalData.Add("contentType", documentEntity.ContentTypeAlias); } @@ -238,7 +239,6 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, true); - AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 2ba11997e6..a2179be75f 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) @@ -116,21 +116,11 @@ namespace Umbraco.Web.Trees if (enableInheritedDocumentTypes) { menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } } - else + //no move action if this is a child doc type + if (parent == null) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias))); - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias))); menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true); diff --git a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs index 160f2a36d8..1b485aea6a 100644 --- a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs @@ -11,6 +11,11 @@ namespace Umbraco.Web.Trees [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] internal class CoreTreeAttribute : Attribute { + public string TreeGroup { get; set; } + public CoreTreeAttribute() + { + + } } } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index a2201f792a..3087808f10 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:3)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class DataTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index 27039832c9..ca9a54f873 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] [Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null, sortOrder: 0)] public class DictionaryTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index b65906d152..5b0ed8701f 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Languages)] [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 11)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class LanguageTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/LegacyTreeController.cs b/src/Umbraco.Web/Trees/LegacyTreeController.cs deleted file mode 100644 index 87035e0632..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeController.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - /// - /// This is used to output JSON from legacy trees - /// - [PluginController("UmbracoTrees"), LegacyTreeAuthorizeAttribute] - public class LegacyTreeController : TreeControllerBase - { - private readonly XmlTreeNode _xmlTreeNode; - private readonly string _currentSection; - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - return LegacyTreeDataConverter.ConvertFromLegacy( - _xmlTreeNode.NodeID, - _xmlTreeNode, - Url, - _currentSection, - queryStrings, - isRoot: true); - } - - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var tree = GetTree(queryStrings); - var attempt = tree.TryLoadFromLegacyTree(id, queryStrings, Url, tree.ApplicationAlias); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render tree {TreeType} for node id {NodeId}", queryStrings.GetRequiredString("treeType"), id); - throw new ApplicationException("Could not render tree " + queryStrings.GetRequiredString("treeType") + " for node id " + id); - } - - return attempt.Result; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - //get the parent id from the query strings - var parentId = queryStrings.GetRequiredString("parentId"); - var tree = GetTree(queryStrings); - - var rootIds = new[] - { - Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinContent.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinMedia.ToString(CultureInfo.InvariantCulture) - }; - - //if the id and the parentId are both -1 then we need to get the menu for the root node - if (rootIds.Contains(id) && parentId == "-1") - { - var attempt = tree.TryGetMenuFromLegacyTreeRootNode(queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for root node for treeType {TreeType}", queryStrings.GetRequiredString("treeType")); - throw new ApplicationException("Could not render menu for root node for treeType " + queryStrings.GetRequiredString("treeType")); - } - - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - else - { - var attempt = tree.TryGetMenuFromLegacyTreeNode(parentId, id, queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for treeType {TreeType} for node id {ParentNodeId}", queryStrings.GetRequiredString("treeType"), parentId); - throw new ApplicationException("Could not render menu for treeType " + queryStrings.GetRequiredString("treeType") + " for node id " + parentId); - } - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - } - - public override string RootNodeDisplayName { get; } - - public override string TreeAlias { get; } - - private ApplicationTree GetTree(FormDataCollection queryStrings) - { - //need to ensure we have a tree type - var treeType = queryStrings.GetRequiredString("treeType"); - //now we'll look up that tree - var tree = Services.ApplicationTreeService.GetByAlias(treeType); - if (tree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - return tree; - } - - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index a0259ab247..131c7954c8 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Routing; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Composing; @@ -19,154 +17,6 @@ namespace Umbraco.Web.Trees /// internal class LegacyTreeDataConverter { - internal static BaseTree GetLegacyTreeForLegacyServices(Core.Models.ApplicationTree appTree) - { - if (appTree == null) throw new ArgumentNullException("appTree"); - - BaseTree tree; - - var controllerAttempt = appTree.TryGetControllerTree(); - if (controllerAttempt.Success) - { - Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services."); - return null; - - //var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); - //if (legacyAtt == null) - //{ - // Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); - // return null; - //} - - //var treeDef = new TreeDefinition( - // legacyAtt.BaseTreeType, - // new ApplicationTree(true, appTree.SortOrder, appTree.ApplicationAlias, appTree.Alias, appTree.Title, appTree.IconClosed, appTree.IconOpened, legacyAtt.BaseTreeType.GetFullNameWithAssembly()), - // new Section(appTree.Alias, appTree.Alias, "", 0)); - - //tree = treeDef.CreateInstance(); - //tree.TreeAlias = appTree.Alias; - - } - else - { - //get the tree that we need to render - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - if (treeDef == null) - { - return null; - } - tree = treeDef.CreateInstance(); - } - - return tree; - } - - /// - /// This is used by any legacy services that require rendering a BaseTree, if a new controller tree is detected it will try to invoke it's legacy predecessor. - /// - /// - /// - /// - internal static BaseTree GetLegacyTreeForLegacyServices(IApplicationTreeService appTreeService, string treeType) - { - if (appTreeService == null) throw new ArgumentNullException("appTreeService"); - if (treeType == null) throw new ArgumentNullException("treeType"); - - //first get the app tree definition so we can then figure out if we need to load by legacy or new - //now we'll look up that tree - var appTree = appTreeService.GetByAlias(treeType); - if (appTree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - - return GetLegacyTreeForLegacyServices(appTree); - } - - /// - /// Gets the menu item collection from a legacy tree node based on it's parent node's child collection - /// - /// The node id - /// The node collection that contains the node id - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(string nodeId, XmlTree xmlTree, string currentSection) - { - var xmlTreeNode = xmlTree.treeCollection.FirstOrDefault(x => x.NodeID == nodeId); - if (xmlTreeNode == null) - { - return null; - } - - return ConvertFromLegacyMenu(xmlTreeNode, currentSection); - } - - /// - /// Gets the menu item collection from a legacy tree node - /// - /// - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(XmlTreeNode xmlTreeNode, string currentSection) - { - var collection = new MenuItemCollection(); - - var menuItems = xmlTreeNode.Menu.ToArray(); - var numAdded = 0; - var seperators = new List(); - foreach (var t in menuItems) - { - if (t is ContextMenuSeperator && numAdded > 0) - { - //store the index for which the seperator should be placed - seperators.Add(collection.Items.Count()); - } - else - { - var menuItem = collection.Items.Add(t, Current.Services.TextService.Localize("actions", t.Alias)); - - var currentAction = t; - - // try to get a URL/title from the legacy action - var attempt = GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection); - if (attempt) - { - var action = attempt.Result; - menuItem.LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = GetLegacyConfirmView(currentAction); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - menuItem.LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + xmlTreeNode.Text + "' ?"); - } - else - { - // if that doesn't work and there's no jsAction in there already then add the legacy js method call - if (menuItem.AdditionalData.ContainsKey(MenuItem.JsActionKey) == false) - menuItem.ExecuteLegacyJs(menuItem.Action.JsFunctionName); - } - } - - numAdded++; - } - } - var length = collection.Items.Count(); - foreach (var s in seperators) - { - if (length >= s) - { - collection.Items.ElementAt(s).SeperatorBefore = true; - } - } - - return collection; - } - - - /// /// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible /// @@ -261,101 +111,6 @@ namespace Umbraco.Web.Trees return Attempt.Fail(); } - /// - /// Converts a legacy XmlTreeNode to a new TreeNode - /// - /// - /// - /// - /// - /// - /// The current query strings for the request - this is used to append the query strings to the menu URL of the item being rendered since the menu - /// actually belongs to this same node (request) the query strings need to exist so the menu can be rendered in some cases. - /// - /// - /// - internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings, bool isRoot = false) - { - // /umbraco/tree.aspx?rnd=d0d0ff11a1c347dabfaa0fc75effcc2a&id=1046&treeType=content&contextMenu=false&isDialog=false - - //we need to convert the node source to our legacy tree controller - var childNodesSource = urlHelper.GetUmbracoApiService("GetNodes"); - - var childQuery = (xmlTreeNode.Source.IsNullOrWhiteSpace() || xmlTreeNode.Source.IndexOf('?') == -1) - ? "" - : xmlTreeNode.Source.Substring(xmlTreeNode.Source.IndexOf('?')); - - //append the query strings - childNodesSource = childNodesSource.AppendQueryStringToUrl(childQuery); - - //for the menu source we need to detect if this is a root node since we'll need to set the parentId and id to -1 - // for which we'll handle correctly on the server side. - //if there are no menu items, then this will be empty - var menuSource = ""; - if (xmlTreeNode.Menu != null && xmlTreeNode.Menu.Any()) - { - menuSource = urlHelper.GetUmbracoApiService("GetMenu"); - //these are the absolute required query strings - var menuQueryStrings = new Dictionary - { - {"id", (isRoot ? "-1" : xmlTreeNode.NodeID)}, - {"treeType", xmlTreeNode.TreeType}, - {"parentId", (isRoot ? "-1" : parentId)}, - {"section", currentSection} - }; - //append the extra ones on this request - foreach (var i in currentQueryStrings.Where(x => menuQueryStrings.Keys.Contains(x.Key) == false)) - { - menuQueryStrings.Add(i.Key, i.Value); - } - - menuSource = menuSource.AppendQueryStringToUrl(menuQueryStrings.ToQueryString()); - } - - - //TODO: Might need to add stuff to additional attributes - - var node = new TreeNode(xmlTreeNode.NodeID, isRoot ? null : parentId, childNodesSource, menuSource) - { - HasChildren = xmlTreeNode.HasChildren, - Icon = xmlTreeNode.Icon, - Name = xmlTreeNode.Text, - NodeType = xmlTreeNode.NodeType - }; - if (isRoot) - { - node.AdditionalData.Add("treeAlias", xmlTreeNode.TreeType); - } - - foreach (var appliedClass in xmlTreeNode.Style.AppliedClasses) - { - node.CssClasses.Add(appliedClass); - } - - //This is a special case scenario, we know that content/media works based on the normal Belle routing/editing so we'll ensure we don't - // pass in the legacy JS handler so we do it the new way, for all other trees (Currently, this is a WIP), we'll render - // the legacy js callback,. - var knownNonLegacyNodeTypes = new[] { "content", "contentRecycleBin", "mediaRecyleBin", "media" }; - if (knownNonLegacyNodeTypes.InvariantContains(xmlTreeNode.NodeType) == false) - { - node.AssignLegacyJsCallback(xmlTreeNode.Action); - } - return node; - } - - internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings) - { - //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert - // that over to use the old school ui.xml stuff for these old trees and however the old menu items worked. - - var collection = new TreeNodeCollection(); - foreach (var x in xmlTree.treeCollection) - { - collection.Add(ConvertFromLegacy(parentId, x, urlHelper, currentSection, currentQueryStrings)); - } - return collection; - } - internal class LegacyUrlAction { public LegacyUrlAction(string url, string dialogTitle) diff --git a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs deleted file mode 100644 index 0ae9916d4b..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Logging; -using umbraco.cms.presentation.Trees; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Web.Trees -{ - /// - /// A class used to render the legacy JS requirements for trees and IActions. - /// - internal static class LegacyTreeJavascript - { - /// - /// If any legacy tree requires any JS rendering then we will compile a JS output of the combination. - /// - /// - public static string GetLegacyTreeJavascript() - { - //find all tree defs that exists for the current application regardless of if they are active - List appTreeDefs = TreeDefinitionCollection.Instance; - //Create the BaseTree's based on the tree definitions found - var legacyTrees = appTreeDefs.Select(treeDef => treeDef.CreateInstance()).ToList(); - var javascript = new StringBuilder(); - foreach (var bTree in legacyTrees) - { - try - { - bTree.RenderJS(ref javascript); - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Could not load the JS from the legacy tree {TreeAlias}", bTree.TreeAlias); - } - } - - return ReplaceLegacyJs(javascript.ToString()); - } - - /// - /// Returns a string with javascript proxy methods for IActions that are using old javascript - /// - /// - public static string GetLegacyIActionJavascript() - { - var js = new StringBuilder(); - foreach (var a in Current.Actions) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (string.IsNullOrEmpty(a.Alias) == false && (string.IsNullOrEmpty(a.JsFunctionName) == false || string.IsNullOrEmpty(a.JsSource) == false)) - { - // if the action is using invalid javascript we need to do something about this - if (global::Umbraco.Web._Legacy.Actions.Action.ValidateActionJs(a) == false) - { - js.AppendLine("function IActionProxy_" + a.Alias.ToSafeAlias() + "() {"); - js.AppendLine(global::Umbraco.Web._Legacy.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); - js.AppendLine("}"); - } - } - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Error initializing tree action"); - } - } - - if (js.Length != 0) - { - js.Insert(0, "// This javascript is autogenerated by Umbraco to ensure legacy compatiblity with old context menu items\n\n"); - } - - return ReplaceLegacyJs(js.ToString()); - } - - private static string ReplaceLegacyJs(string js){ - js = js.Replace("parent.right.document.location", "UmbClientMgr.getFakeFrame()"); - js = js.Replace("right.document.location", "UmbClientMgr.getFakeFrame()"); - - return js; - } - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeParams.cs b/src/Umbraco.Web/Trees/LegacyTreeParams.cs deleted file mode 100644 index 13b84edd38..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeParams.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - - //Temporary, but necessary until we refactor trees in general - internal class LegacyTreeParams : ITreeService - { - public LegacyTreeParams() - { - - } - - public LegacyTreeParams(IEnumerable> formCollection) - { - if (formCollection != null) - { - var p = TreeRequestParams.FromDictionary(formCollection.ToDictionary(x => x.Key, x => x.Value)); - NodeKey = p.NodeKey; - StartNodeID = p.StartNodeID; - ShowContextMenu = p.ShowContextMenu; - IsDialog = p.IsDialog; - DialogMode = p.DialogMode; - FunctionToCall = p.FunctionToCall; - } - } - - public string NodeKey { get; set; } - public int StartNodeID { get; set; } - public bool ShowContextMenu { get; set; } - public bool IsDialog { get; set; } - public TreeDialogModes DialogMode { get; set; } - public string FunctionToCall { get; set; } - } -} diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 66f92ffdc0..6a612ba8fb 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; using Umbraco.Core.Models; @@ -15,9 +16,8 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Macros)] [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] - [CoreTree] - public class - MacrosTreeController : TreeController + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + public class MacrosTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 55a15b683c..7b346c6871 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:1)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 56b836ce8a..77eb6e0b24 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -7,6 +7,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup =Constants.Trees.Groups.Settings)] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)] public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 882cfb2c9f..c874b01244 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewMacrosTreeController : PartialViewsTreeController { protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem; diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index 41c53fdc99..6a65f5dd3e 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 7)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; @@ -29,7 +29,7 @@ namespace Umbraco.Web.Trees { //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - treeNode.Icon = "icon-article"; + treeNode.Icon = "icon-folder"; } } } diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index e35a9a23b6..4930b4dc5e 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class RelationTypeTreeController : TreeController { protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs index 97053993b4..08a03ac912 100644 --- a/src/Umbraco.Web/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -6,6 +6,7 @@ using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 10)] public class ScriptsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs index 365f427e18..548e8ae928 100644 --- a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -4,6 +4,7 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 9)] public class StylesheetsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 521475413c..768768f888 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Templates)] [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:6)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class TemplatesTreeController : TreeController, ISearchableTree { /// diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index db1bca0234..e6bd53ddf8 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -33,39 +33,14 @@ namespace Umbraco.Web.Trees protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - var nodes = new TreeNodeCollection(); - return nodes; + //full screen app without tree nodes + return TreeNodeCollection.Empty; } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //Create User - var createMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("actions/create")); - createMenuItem.Icon = "add"; - createMenuItem.NavigateToRoute("users/users/overview?subview=users&create=true"); - menu.Items.Add(createMenuItem); - - //This is the same setting used in the global JS for 'showUserInvite' - if (EmailSender.CanSendRequiredEmail) - { - //Invite User (Action import closest type of action to an invite user) - var inviteMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("user/invite")); - inviteMenuItem.Icon = "message-unopened"; - inviteMenuItem.NavigateToRoute("users/users/overview?subview=users&invite=true"); - - menu.Items.Add(inviteMenuItem); - } - - return menu; - } - - //There is no context menu options for editing a specific user - //Also we no longer list each user in the tree & in theory never hit this - return menu; + //doesn't have a menu, this is a full screen app without tree nodes + return MenuItemCollection.Empty; } } } diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs deleted file mode 100644 index 2fe08e25fb..0000000000 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Web._Legacy.Actions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Users)] - [Tree(Constants.Applications.Users, Constants.Trees.Users, null)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class UsersTreeController : TreeController - { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - - var users = new List(Services.UserService.GetAll(0, int.MaxValue, out _)); - var currentUser = UmbracoContext.Current.Security.CurrentUser; - var hideDisabledUsers = UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; - - foreach (var user in users.OrderBy(x => x.IsApproved == false)) - { - // hide disabled user - if (user.IsApproved == false && hideDisabledUsers) - continue; - - if (user.IsSuper()) - { - // only super can see super - if (!currentUser.IsSuper()) continue; - } - else if (user.IsAdmin()) - { - // only admins can see admins - if (!currentUser.IsAdmin()) continue; - } - - var node = CreateTreeNode( - user.Id.ToString(CultureInfo.InvariantCulture), - "-1", - queryStrings, - user.Name, - "icon-user", - false, - "/" + queryStrings.GetValue("application") + "/framed/" - + Uri.EscapeDataString("users/EditUser.aspx?id=" + user.Id)); - - if (user.IsApproved == false) - node.CssClasses.Add("not-published"); - - nodes.Add(node); - } - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - // Root actions - menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) - .ConvertLegacyMenuItem(null, "users", queryStrings.GetValue("application")); - - menu.Items.Add( - Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); - return menu; - } - - // If administator, don't create a menu - if (id == "0") - return menu; - - menu.Items.Add(new DisableUser() - { - Name = Services.TextService.Localize("actions", "disable") - }); - - return menu; - } - } -} diff --git a/src/Umbraco.Web/TypeLoaderExtensions.cs b/src/Umbraco.Web/TypeLoaderExtensions.cs index a1209abccf..710c342115 100644 --- a/src/Umbraco.Web/TypeLoaderExtensions.cs +++ b/src/Umbraco.Web/TypeLoaderExtensions.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Media; using Umbraco.Web.Mvc; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Web._Legacy.Actions; @@ -43,17 +42,7 @@ namespace Umbraco.Web { return mgr.GetTypes(); } - - /// - /// Returns all available ITrees in application - /// - /// - /// - internal static IEnumerable GetTrees(this TypeLoader mgr) - { - return mgr.GetTypes(); - } - + /// /// Returns all available ISearchableTrees in application /// diff --git a/src/Umbraco.Web/UI/Controls/ProgressBar.cs b/src/Umbraco.Web/UI/Controls/ProgressBar.cs index 0c97e96644..a40509cfb8 100644 --- a/src/Umbraco.Web/UI/Controls/ProgressBar.cs +++ b/src/Umbraco.Web/UI/Controls/ProgressBar.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.UI.Controls protected override void Render(System.Web.UI.HtmlTextWriter writer) { // fixme - image is gone! - base.ImageUrl = SystemDirectories.UmbracoClient + "/images/progressBar.gif"; + base.ImageUrl = "/images/progressBar.gif"; base.AlternateText = Title; base.Render(writer); diff --git a/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs b/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs index 7b8397d047..dc3a2efbc7 100644 --- a/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs +++ b/src/Umbraco.Web/UI/JavaScript/UmbracoClientDependencyLoader.cs @@ -17,7 +17,6 @@ namespace Umbraco.Web.UI.JavaScript public UmbracoClientDependencyLoader() : base() { - this.AddPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient)); this.AddPath("UmbracoRoot", IOHelper.ResolveUrl(SystemDirectories.Umbraco)); this.ProviderName = LoaderControlProvider.DefaultName; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 003102bc40..4273cee9a4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -150,6 +150,7 @@ + @@ -179,7 +180,9 @@ + + @@ -201,7 +204,6 @@ - @@ -513,13 +515,9 @@ - - - ASPXCodeBehind - ASPXCodeBehind @@ -641,7 +639,6 @@ - @@ -965,11 +962,7 @@ - - - - @@ -1038,18 +1031,12 @@ - - ASPXCodeBehind - ASPXCodeBehind ASPXCodeBehind - - ASPXCodeBehind - @@ -1209,9 +1196,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1269,21 +1253,8 @@ - - ASPXCodeBehind - - - Component - - - - - Code - - - FeedProxy.aspx ASPXCodeBehind @@ -1344,29 +1315,11 @@ editPackage.aspx - - rollBack.aspx - - - rollBack.aspx - - - Code - - - - - - - - - - CheckForUpgrade.asmx @@ -1425,7 +1378,6 @@ - ASPXCodeBehind @@ -1434,7 +1386,6 @@ - ASPXCodeBehind @@ -1448,9 +1399,6 @@ ASPXCodeBehind - - ASPXCodeBehind - @@ -1504,10 +1452,7 @@ umbraco_org_umbraco_update_CheckForUpgrade - - - - +