diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index 6c266ced2a..3a673e92a2 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -22,13 +22,13 @@ namespace Umbraco.Core.Deploy /// /// The datatype. /// The dependencies. - IDictionary ToArtifact(IDataType dataType, ICollection dependencies); + string ToArtifact(IDataType dataType, ICollection dependencies); /// /// Gets the actual datatype configuration corresponding to the artifact configuration. /// /// The datatype. /// The artifact configuration. - object FromArtifact(IDataType dataType, IDictionary configuration); + object FromArtifact(IDataType dataType, string configuration); } } diff --git a/src/Umbraco.Core/EnumExtensions.cs b/src/Umbraco.Core/EnumExtensions.cs new file mode 100644 index 0000000000..1443a27876 --- /dev/null +++ b/src/Umbraco.Core/EnumExtensions.cs @@ -0,0 +1,45 @@ +using System; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods to enums. + /// + public static class EnumExtensions + { + // note: + // - no need to HasFlagExact, that's basically an == test + // - HasFlagAll cannot be named HasFlag because ext. methods never take priority over instance methods + + /// + /// Determines whether a flag enum has all the specified values. + /// + /// + /// True when all bits set in are set in , though other bits may be set too. + /// This is the behavior of the original method. + /// + public static bool HasFlagAll(this T use, T uses) + where T : Enum + { + var num = Convert.ToUInt64(use); + var nums = Convert.ToUInt64(uses); + + return (num & nums) == nums; + } + + /// + /// Determines whether a flag enum has any of the specified values. + /// + /// + /// True when at least one of the bits set in is set in . + /// + public static bool HasFlagAny(this T use, T uses) + where T : Enum + { + var num = Convert.ToUInt64(use); + var nums = Convert.ToUInt64(uses); + + return (num & nums) > 0; + } + } +} diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index 5bbf325c8c..100dbc265e 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -39,9 +39,6 @@ namespace Umbraco.Core.IO [Obsolete("Usercontrols are obsolete and code should be removed")] public static string UserControls => "~/usercontrols"; - [Obsolete("Only used by legacy load balancing which is obsolete and should be removed")] - public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices"); - public static string Packages => Data + "/packages"; public static string Preview => Data + "/preview"; diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 463e5a6a5c..95c8d3e17f 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -737,7 +737,8 @@ namespace Umbraco.Core /// /// Refers to itself /// The MD5 hashed string - public static string ToMd5(this string stringToConvert) + [Obsolete("Please use the GenerateHash method instead. This may be removed in future versions")] + internal static string ToMd5(this string stringToConvert) { return stringToConvert.GenerateHash("MD5"); } @@ -747,7 +748,8 @@ namespace Umbraco.Core /// /// refers to itself /// The SHA1 hashed string - public static string ToSHA1(this string stringToConvert) + [Obsolete("Please use the GenerateHash method instead. This may be removed in future versions")] + internal static string ToSHA1(this string stringToConvert) { return stringToConvert.GenerateHash("SHA1"); } diff --git a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs deleted file mode 100644 index 38ba2ce189..0000000000 --- a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Services; -using Umbraco.Core.IO; - -namespace Umbraco.Core.Sync -{ - /// - /// The client Soap service for making distributed cache calls between servers - /// - [WebServiceBinding(Name = "CacheRefresherSoap", Namespace = "http://umbraco.org/webservices/")] - [Obsolete("Legacy load balancing is obsolete and should be removed")] - internal class ServerSyncWebServiceClient : System.Web.Services.Protocols.SoapHttpClientProtocol - { - - /// - public ServerSyncWebServiceClient() - { - // only set the url if the httpcontext is present, else it's set by the cache dispatcher methods (when using distributed calls) - if (System.Web.HttpContext.Current != null) - this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/cacheRefresher.asmx"; - - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/BulkRefresh", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password) - { - this.Invoke("BulkRefresh", new object[] { - instructions, - appId, - login, - password}); - } - - /// - public System.IAsyncResult BeginBulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("BulkRefresh", new object[] { - instructions, - appId, - login, - password}, callback, asyncState); - } - - /// - public void EndBulkRefresh(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RefreshAll(System.Guid uniqueIdentifier, string Login, string Password) - { - this.Invoke("RefreshAll", new object[] { - uniqueIdentifier, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRefreshAll(System.Guid uniqueIdentifier, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshAll", new object[] { - uniqueIdentifier, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRefreshAll(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByJson", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RefreshByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password) - { - this.Invoke("RefreshByJson", new object[] { - uniqueIdentifier, - jsonPayload, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRefreshByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshByJson", new object[] { - uniqueIdentifier, - jsonPayload, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRefreshByJson(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password) - { - this.Invoke("RefreshByGuid", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshByGuid", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRefreshByGuid(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshById", - RequestNamespace = "http://umbraco.org/webservices/", - ResponseNamespace = "http://umbraco.org/webservices/", - Use = System.Web.Services.Description.SoapBindingUse.Literal, - ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password) - { - this.Invoke("RefreshById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRefreshById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRefreshById(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByIds", - RequestNamespace = "http://umbraco.org/webservices/", - ResponseNamespace = "http://umbraco.org/webservices/", - Use = System.Web.Services.Description.SoapBindingUse.Literal, - ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password) - { - this.Invoke("RefreshByIds", new object[] { - uniqueIdentifier, - jsonIds, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRefreshByIds(System.Guid uniqueIdentifier, string jsonIds, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RefreshByIds", new object[] { - uniqueIdentifier, - jsonIds, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRefreshByIds(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveById", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public void RemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password) - { - this.Invoke("RemoveById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}); - } - - /// - public System.IAsyncResult BeginRemoveById(System.Guid uniqueIdentifier, int Id, string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("RemoveById", new object[] { - uniqueIdentifier, - Id, - Login, - Password}, callback, asyncState); - } - - /// - public void EndRemoveById(System.IAsyncResult asyncResult) - { - this.EndInvoke(asyncResult); - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/GetRefreshers", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public System.Xml.XmlNode GetRefreshers(string Login, string Password) - { - object[] results = this.Invoke("GetRefreshers", new object[] { - Login, - Password}); - return ((System.Xml.XmlNode)(results[0])); - } - - /// - public System.IAsyncResult BeginGetRefreshers(string Login, string Password, System.AsyncCallback callback, object asyncState) - { - return this.BeginInvoke("GetRefreshers", new object[] { - Login, - Password}, callback, asyncState); - } - - /// - public System.Xml.XmlNode EndGetRefreshers(System.IAsyncResult asyncResult) - { - object[] results = this.EndInvoke(asyncResult); - return ((System.Xml.XmlNode)(results[0])); - } - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e31579c9f7..4662ca0403 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -319,6 +319,7 @@ + @@ -1524,9 +1525,6 @@ Component - - Component - Properties\SolutionInfo.cs diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index ba3e677219..083ca90cc4 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -4,7 +4,6 @@ using System.Linq; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Core.Logging; namespace Umbraco.Core { @@ -28,7 +27,6 @@ namespace Umbraco.Core /// If any route has an extension in the path like .aspx = back office /// /// These are def back office: - /// /Umbraco/RestServices = back office /// /Umbraco/BackOffice = back office /// /Umbraco/Preview = back office /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end @@ -78,7 +76,6 @@ namespace Umbraco.Core //check for special back office paths if (urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/BackOffice/") - || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/RestServices/") || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/Preview/")) { return true; diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 7271db9b61..ca622e9288 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -366,7 +366,6 @@ namespace Umbraco.Tests.Composing "umbraco.interfaces,", "umbraco.providers,", "Umbraco.Web.UI,", - "umbraco.webservices", "Lucene.", "Examine,", "Examine.", diff --git a/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs new file mode 100644 index 0000000000..72a55cee85 --- /dev/null +++ b/src/Umbraco.Tests/CoreThings/EnumExtensionsTests.cs @@ -0,0 +1,55 @@ +using NUnit.Framework; +using Umbraco.Web.Trees; +using Umbraco.Core; + +namespace Umbraco.Tests.CoreThings +{ + [TestFixture] + public class EnumExtensionsTests + { + [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Main, false)] + [TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, false)] + public void HasFlagTest(TreeUse value, TreeUse test, bool expected) + { + // the built-in Enum.HasFlag() method determines whether + // all bits from are set (other bits can be set too) + + if (expected) + Assert.IsTrue(value.HasFlag(test)); + else + Assert.IsFalse(value.HasFlag(test)); + } + + [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Main, false)] + [TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, false)] + public void HasFlagAllTest(TreeUse value, TreeUse test, bool expected) + { + // the HasFlagAll() extension method determines whether + // all bits from are set (other bits can be set too) + + if (expected) + Assert.IsTrue(value.HasFlagAll(test)); + else + Assert.IsFalse(value.HasFlagAll(test)); + } + + [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Main, false)] + [TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)] + [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, true)] + public void HasFlagAnyTest(TreeUse value, TreeUse test, bool expected) + { + // the HasFlagAny() extension method determines whether + // at least one bit from is set + + if (expected) + Assert.IsTrue(value.HasFlagAny(test)); + else + Assert.IsFalse(value.HasFlagAny(test)); + } + } +} diff --git a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs index f253d44973..be7aec631e 100644 --- a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs @@ -33,7 +33,6 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/umbrac", "", false)] [TestCase("http://www.domain.com/test", "", false)] [TestCase("http://www.domain.com/test/umbraco", "", false)] - [TestCase("http://www.domain.com/Umbraco/restServices/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/anything", "", true)] [TestCase("http://www.domain.com/Umbraco/anything/", "", true)] @@ -42,8 +41,6 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] - [TestCase("http://www.domain.com/MyVdir/Umbraco/restServices/blah", "/myvdir", true)] - [TestCase("http://www.domain.com/umbraco/webservices/legacyAjaxCalls.asmx/js", "", true)] [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] public void Is_Back_Office_Request(string input, string virtualPath, bool expected) { diff --git a/src/Umbraco.Tests/IO/IoHelperTests.cs b/src/Umbraco.Tests/IO/IoHelperTests.cs index b0e59cbb55..3f510dd57e 100644 --- a/src/Umbraco.Tests/IO/IoHelperTests.cs +++ b/src/Umbraco.Tests/IO/IoHelperTests.cs @@ -44,7 +44,6 @@ namespace Umbraco.Tests.IO 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.UserControls, true), IOHelper.MapPath(SystemDirectories.UserControls, false)); - Assert.AreEqual(IOHelper.MapPath(SystemDirectories.WebServices, true), IOHelper.MapPath(SystemDirectories.WebServices, false)); } [Test] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e2afb61839..0da14aaef2 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -69,6 +69,7 @@ + @@ -122,6 +123,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 2970c52bbb..b749518660 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -137,7 +137,7 @@ In the following example you see how to run some custom logic before a step goes
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index 8ad71226ba..6038eb1ec0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -14,7 +14,7 @@ Use this directive to render an umbraco button. The directive can be used to gen diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index a65b94444e..78e9cabcfc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -25,7 +25,7 @@ @import "../../lib/bootstrap/less/close.less"; // Components: Buttons & Alerts -@import "../../lib/bootstrap/less/button-groups.less"; +@import "button-groups.less"; @import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less // Components: Nav diff --git a/src/Umbraco.Web.UI.Client/src/less/button-groups.less b/src/Umbraco.Web.UI.Client/src/less/button-groups.less new file mode 100644 index 0000000000..070e353c14 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/button-groups.less @@ -0,0 +1,229 @@ +// +// Button groups +// -------------------------------------------------- + + +// Make the div behave like a button +.btn-group { + position: relative; + display: inline-block; + .ie7-inline-block(); + font-size: 0; // remove as part 1 of font-size inline-block hack + vertical-align: middle; // match .btn alignment given font-size hack above + white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) + .ie7-restore-left-whitespace(); +} + +// Space out series of button groups +.btn-group + .btn-group { + margin-left: 5px; +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + font-size: 0; // Hack to remove whitespace that results from using inline-block + margin-top: @baseLineHeight / 2; + margin-bottom: @baseLineHeight / 2; + > .btn + .btn, + > .btn-group + .btn, + > .btn + .btn-group { + margin-left: 5px; + } +} + +// Float them, remove border radius, then re-add to first and last elements +.btn-group > .btn { + position: relative; + .border-radius(0); +} +.btn-group > .btn + .btn { + margin-left: -1px; +} +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack +} + +// Reset fonts for other sizes +.btn-group > .btn-mini { + font-size: @fontSizeMini; +} +.btn-group > .btn-small { + font-size: @fontSizeSmall; +} +.btn-group > .btn-large { + font-size: @fontSizeLarge; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + .border-top-left-radius(@baseBorderRadius); + .border-bottom-left-radius(@baseBorderRadius); +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + .border-top-right-radius(@baseBorderRadius); + .border-bottom-right-radius(@baseBorderRadius); +} +// Reset corners for large buttons +.btn-group > .btn.large:first-child { + margin-left: 0; + .border-top-left-radius(@borderRadiusLarge); + .border-bottom-left-radius(@borderRadiusLarge); +} +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + .border-top-right-radius(@borderRadiusLarge); + .border-bottom-right-radius(@borderRadiusLarge); +} + +// On hover/focus/active, bring the proper btn to front +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group > .btn-mini + .dropdown-toggle { + padding-left: 5px; + padding-right: 5px; + *padding-top: 2px; + *padding-bottom: 2px; +} +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} +.btn-group > .btn-large + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; + *padding-top: 7px; + *padding-bottom: 7px; +} + +.btn-group.open { + + // The clickable button for toggling the menu + // Remove the gradient and set the same inset shadow as the :active state + .dropdown-toggle { + background-image: none; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Keep the hover's background when dropdown is open + .btn.dropdown-toggle { + background-color: @btnBackgroundHighlight; + } + .btn-primary.dropdown-toggle { + background-color: @btnPrimaryBackgroundHighlight; + } + .btn-warning.dropdown-toggle { + background-color: @btnWarningBackgroundHighlight; + } + .btn-danger.dropdown-toggle { + background-color: @btnDangerBackgroundHighlight; + } + .btn-success.dropdown-toggle { + background-color: @btnSuccessBackgroundHighlight; + } + .btn-info.dropdown-toggle { + background-color: @btnInfoBackgroundHighlight; + } + .btn-inverse.dropdown-toggle { + background-color: @btnInverseBackgroundHighlight; + } +} + + +// Reposition the caret +.btn .caret { + margin-top: 8px; + margin-left: 0; +} +// Carets in other button sizes +.btn-large .caret { + margin-top: 6px; +} +.btn-large .caret { + border-left-width: 5px; + border-right-width: 5px; + border-top-width: 5px; +} +.btn-mini .caret, +.btn-small .caret { + margin-top: 8px; +} +// Upside down carets for .dropup +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + + +/* +// Account for other colors +.btn-primary, +.btn-warning, +.btn-danger, +.btn-info, +.btn-success, +.btn-inverse { + .caret { + border-top-color: @white; + border-bottom-color: @white; + } +} +*/ + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + display: inline-block; // makes buttons only take up the width they need + .ie7-inline-block(); +} +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + .border-radius(0); +} +.btn-group-vertical > .btn + .btn { + margin-left: 0; + margin-top: -1px; +} +.btn-group-vertical > .btn:first-child { + .border-radius(@baseBorderRadius @baseBorderRadius 0 0); +} +.btn-group-vertical > .btn:last-child { + .border-radius(0 0 @baseBorderRadius @baseBorderRadius); +} +.btn-group-vertical > .btn-large:first-child { + .border-radius(@borderRadiusLarge @borderRadiusLarge 0 0); +} +.btn-group-vertical > .btn-large:last-child { + .border-radius(0 0 @borderRadiusLarge @borderRadiusLarge); +} diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 0b21864127..51dcce7b92 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -189,11 +189,19 @@ input[type="button"] { } // Success appears as green .btn-success { - .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight, @btnSuccessType); } // Info appears as a neutral blue .btn-info { - .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); + .buttonBackground(@sand-5, @blueDark, @blueExtraDark, @u-white); +} +// Made for Umbraco, 2019 +.btn-action { + .buttonBackground(@pinkLight, @blueDark, @blueExtraDark, @u-white); +} +// Made for Umbraco, 2019, used for buttons that has to stand back. +.btn-white { + .buttonBackground(@btnWhiteBackground, @btnWhiteBackgroundHighlight, @btnWhiteType, @btnWhiteTypeHover); } // Inverse appears as dark gray .btn-inverse { @@ -226,25 +234,27 @@ input[type="button"] { background: @green; color: @white; font-weight: bold; - &:hover { + &:hover { background: @green-d1; - } + } } // outlined .btn-outline { - border: 1px solid @gray-8; + border: 1px solid; + border-color: @gray-7; background: @white; - color: @black; + color: @blueExtraDark; padding: 5px 13px; + transition: all .2s linear; } .btn-outline:hover, .btn-outline:focus, .btn-outline:active { - border-color: @gray-7; - background: transparent; - color: @black; + border-color: @ui-light-type-hover; + color: @ui-light-type-hover; + background: @white; } // Cross-browser Jank 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 cbb38a23b1..a144336a54 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -113,7 +113,7 @@ a, a:hover{ -webkit-box-shadow: none; box-shadow: none; } - +/* .btn-success { color: #ffffff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); @@ -133,7 +133,7 @@ a, a:hover{ .btn-success:hover { color: #ffffff; } - +*/ .btn-group > .btn + .dropdown-toggle { -webkit-box-shadow: none; box-shadow: none; @@ -207,14 +207,14 @@ a, a:hover{ clear: both; font-weight: normal; line-height: 20px; - color: #343434; + color: black; white-space: nowrap; cursor:pointer; } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { color: #000000; - background: #f8f8f8; + background: #e4e0dd; } /****************************/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index a033e8abef..86ead685b8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -1,5 +1,5 @@ .umb-app-header { - background: @purple; + background: @blueExtraDark; display: flex; align-items: center; justify-content: space-between; @@ -31,7 +31,7 @@ } .umb-app-header__action-icon { - opacity: 0.6; + opacity: 0.8; color: @white; font-size: 22px; } @@ -40,4 +40,3 @@ .umb-app-header__action a:focus .umb-app-header__action-icon { opacity: 1; } - diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less index 2a1d5eb6d3..3257d7b93f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-dashboard.less @@ -31,5 +31,5 @@ } .umb-dashboard__header .umb-tabs-nav .umb-tab > a { - padding-bottom: 23px; -} \ No newline at end of file + padding-bottom: 25px; +} 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 2c7f07ef26..8c3b059a94 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 @@ -6,7 +6,7 @@ right: 0; z-index: 10; width: @drawerWidth; - background: @gray-9; + background: @brownGrayLight; box-shadow: inset 5px 0 20px rgba(0,0,0,.3); } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index 78f631ac0d..c950b01602 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -16,17 +16,22 @@ border-bottom: 1px solid @gray-9; height: @editorHeaderHeight; box-sizing: border-box; -} - -.umb-language-picker__toggle:hover { - background: @gray-10; + color: @ui-option-type; } .umb-language-picker__expand { - color: @gray-6; font-size: 14px; } +.umb-language-picker__toggle:hover { + background: @ui-option-hover; + color:@ui-option-type-hover; + .umb-language-picker__expand { + color: @ui-option-type-hover; + } +} + + .umb-language-picker__dropdown { width: 100%; background: @white; @@ -46,11 +51,14 @@ .umb-language-picker__dropdown a:hover, .umb-language-picker__dropdown a:focus { - background: @gray-10; + background: @ui-option-hover; text-decoration: none; + color:@ui-option-type-hover; } -.umb-language-picker__dropdown-item--current { - background-color: @gray-10; - border-left: 2px solid @turquoise; -} \ No newline at end of file +.umb-language-picker__dropdown a.umb-language-picker__dropdown-item--current { + //background-color: @gray-10; + padding-left: 16px; + border-left: 4px solid @ui-light-active-border; + color:@ui-light-active-type; +} 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 a302ba71b8..0f4f2a7a9d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -70,7 +70,7 @@ margin: 0; } -.umb-card-list li{ +.umb-card-list li { border-bottom: @gray-10 1px solid; padding-bottom: 3px; display: block; @@ -123,15 +123,15 @@ .umb-card-grid .umb-card-grid-item:focus, .umb-card-grid .umb-card-grid-item:hover > *, .umb-card-grid .umb-card-grid-item:focus > * { - background: @gray-10; - //color: white; + background: @ui-option-hover; + color: @ui-option-type-hover; cursor: pointer; outline: none; border-radius: 3px; } .umb-card-grid a { - color: @gray-2; + color: @ui-option-type; text-decoration: none; } @@ -139,7 +139,7 @@ font-size: 30px; line-height: 50px; display: block; - color: @gray-3; + color: @ui-option-type; } .umb-card-grid .umb-card-grid-item__loading { @@ -164,9 +164,9 @@ } .umb-btn-round:hover, .umb-btn-round:hover *{ - background: @turquoise !important; + background: @blueDark !important; color: @white !important; - border-color: @turquoise !important; + border-color: @blueDark !important; text-decoration:none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index d699193c24..d576c33b67 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -65,7 +65,7 @@ .umb-editor-header { background: @white; position: absolute; - padding: 0 20px; + padding-left: 20px; z-index: @zIndexEditor; border-bottom: 1px solid @gray-9; width: 100%; @@ -101,9 +101,6 @@ input.umb-editor-header__name-input { padding: 0 10px; background: @white; border: 1px solid @gray-8; - &:hover { - border-color: @turquoise-d1; - } } input.umb-editor-header__name-input:disabled { @@ -118,6 +115,14 @@ input.umb-editor-header__name-input:disabled { } a.umb-editor-header__close-split-view { + + display: flex; + justify-content: center; + align-items: center; + position: relative; + height: 69px; + width: 69px; + font-size: 20px; color: @gray-6; text-decoration: none !important; @@ -127,6 +132,15 @@ a.umb-editor-header__close-split-view:hover { color: @black; } +.umb-editor-header { + .btn-white { + height:69px; + border-radius: 0; + padding-left: 30px; + padding-right: 30px; + } +} + /* variant switcher */ .umb-variant-switcher__toggle { display: flex; @@ -138,22 +152,28 @@ a.umb-editor-header__close-split-view:hover { height: 30px; text-decoration: none !important; font-size: 13px; - color: @gray-4; - background-color: @white; + //color: @gray-4; + color: @ui-action-disgrete-type; + //background-color: @white; } a.umb-variant-switcher__toggle { - transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; &:hover { - background-color: @gray-10; + //background-color: @gray-10; + color: @ui-action-disgrete-type-hover; + .umb-variant-switcher__expand { + color: @ui-action-disgrete-type-hover; + } } } .umb-variant-switcher__expand { - color: @gray-7; + color: @ui-action-disgrete-type; margin-top: 3px; margin-left: 5px; margin-right: -5px; + transition: color 0.2s ease-in-out; } .umb-variant-switcher__item { @@ -161,27 +181,30 @@ a.umb-variant-switcher__toggle { justify-content: space-between; align-items: center; border-bottom: 1px solid @gray-9; + &:hover .umb-variant-switcher__name-wrapper { + + } } .umb-variant-switcher__item:last-child { border-bottom: none; } +.umb-variant-switcher_item--current { + color: @ui-light-active-type; +} .umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper { - background-color: @gray-10; - border-left: 2px solid @turquoise; + //background-color: @gray-10; + border-left: 4px solid @ui-active; } .umb-variant-switcher__item:hover, -.umb-variant-switcher__item:focus, -.umb-variant-switcher__name-wrapper:hover, -.umb-variant-switcher__name-wrapper:focus { - background-color: @gray-10; +.umb-variant-switcher__item:focus { outline: none; } .umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover { - background-color: @white !important; + //background-color: @white !important; cursor: default; } @@ -211,7 +234,12 @@ a.umb-variant-switcher__toggle { .umb-variant-switcher__split-view { font-size: 13px; display: none; - padding: 8px 20px; + padding: 16px 20px; + + &:hover { + background-color: @ui-option-hover; + color: @ui-option-type-hover; + } } // container @@ -223,7 +251,7 @@ a.umb-variant-switcher__toggle { bottom: @editorFooterHeight; left: 0; overflow: auto; - background: @gray-10; + background: @brownGrayLight; } .umb-editor-wrapper.-no-footer .umb-editor-container { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 10296b58e3..2b9f1e31a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -48,10 +48,17 @@ padding-right: 10px; } +.umb-editor-sub-header__content-left .umb-editor-sub-header__section { + padding-left: 0; +} + .umb-editor-sub-header__content-left .umb-editor-sub-header__section:first-child { padding-left: 0; } +.umb-editor-sub-header__content-right .umb-editor-sub-header__section { + padding-right: 0; +} .umb-editor-sub-header__content-right .umb-editor-sub-header__section:last-child { padding-right: 0; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 321fabadad..764094a9b8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -12,7 +12,7 @@ right: 0; bottom: 0; left: 0; - background: @gray-10; + background: @brownGrayLight; z-index: @zIndexEditor; } @@ -44,4 +44,4 @@ left: 0; background: rgba(0,0,0,0.4); z-index: @zIndexEditor; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less index c9aff190ce..934992fc6e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-alert.less @@ -2,20 +2,20 @@ padding: 15px; box-sizing: border-box; background-color: @turquoise-washed; - border: 1px solid @turquoise; + border: 1px solid @blueMid; } .umb-alert--info { - background-color: @turquoise-washed; - border: 1px solid @turquoise; + background-color: @blueLight; + border: 1px solid @blueDark; } .umb-alert--warning { - background-color: @yellow-washed; - border: 1px solid @yellow; + background-color: @pinkLight; + border: 1px solid @pink; } .umb-alert--danger { - background-color: @red-washed; - border: 1px solid @red; + background-color: @pinkLight; + border: 1px solid @pink; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index b2d6cda9b1..6e9c79a0c2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -15,8 +15,10 @@ } .umb-overlay .umb-overlay-header { - background: @gray-10; + //background: @gray-10; border-bottom: 1px solid @purple-l3; + //background: @blueExtraDark; + //color:@u-white; padding: 10px; margin-top: 0; flex-grow: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less index 15296a6aaa..40a0cc41e3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less @@ -23,7 +23,7 @@ .icon { font-size: 18px; vertical-align: middle; - color: @gray-3; + //color: @gray-3; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index 002d076461..097665bdd4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -2,7 +2,11 @@ display: block; min-width: 100%; width: auto; - + + .umb-tree-item__label { + user-select: none; + } + &:hover ins { visibility: visible; cursor: pointer @@ -14,17 +18,17 @@ &:hover .umb-tree-item__label { overflow: hidden; margin-right: 6px; - } + } // Loading Animation // ------------------------ .l { width: 100%; - height: 1px; + height: 2px; overflow: hidden; position: absolute; left: 0; - bottom: 0; + bottom: -1px; div { .umb-loader; @@ -42,13 +46,14 @@ .umb-tree-item.current > .umb-tree-item__inner { - background: @turquoise-d1; + background: @ui-active; + color:@ui-active-type; // override small icon color. TODO => check usage -// &:before { -// color: @turquoise-l2; -// } - + &:before { + color: @blue; + } + .umb-options { &:hover i { @@ -56,8 +61,8 @@ } i { - background: @white; - border-color: @turquoise-d1; + background: @ui-active-type; + //border-color: @ui-active; transition: opacity 120ms ease; } } @@ -65,8 +70,8 @@ a, .umb-tree-icon, ins { - color: @white !important; - background-color: @turquoise-d1; - border-color: @turquoise-d1; + color: @ui-active-type !important; + //background-color: @ui-active; + //border-color: @ui-active; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less index 72a008b8b6..a3529d5504 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less @@ -14,7 +14,6 @@ margin: 0; width: 100%; display: flex; - color: @gray-2; } .umb-options { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 38860e6d22..13e2a55089 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -14,7 +14,7 @@ a, a:hover { outline: none; text-decoration: none; - + // TODO: => confirm not in use // &.noSpr { // background-position: 0 @@ -28,10 +28,11 @@ visibility: hidden; text-decoration: none; font-size: 12px; - transition: opacity 120ms ease; + transition: color 120ms; &:hover { - opacity: .7; + //opacity: .7; + color: @ui-option-type-hover; } } @@ -86,6 +87,23 @@ body.touch .umb-tree { } } +// active is equivilant to selected, its the item that is begin affected by the actions performed in the right-click-dialog. +.umb-tree-item > .umb-tree-item__inner.active { + //background: @ui-selected; + border-color: @ui-selected-border; + color: @ui-selected-type; + a { + color: @ui-selected-type; + } + &:hover { + //background: @ui-selected-hover; + border-color: @ui-selected-border-hover; + color: @ui-selected-type-hover; + a { + color: @ui-selected-type-hover; + } + } +} .umb-tree-root, .umb-tree-item__inner { padding: 0; position: relative; @@ -93,17 +111,29 @@ body.touch .umb-tree { display: flex; flex-wrap: nowrap; align-items: center; - - &.active { - background: @gray-10; + + border:2px dashed transparent; + + color: @ui-option-type; + a { + color: @ui-option-type; } - + &:hover { - background: @gray-10; - + background: @ui-option-hover; + + color: @ui-option-type-hover; + a { + color: @ui-option-type-hover; + } + > .umb-options { visibility: visible; } + + .umb-tree-icon { + color: @ui-option-type-hover; + } } } @@ -245,7 +275,7 @@ body.touch .umb-tree { .is-container::before { content: "\e04e"; - color: @turquoise; + color: @blue; font-size: 8px; padding-left: 13px; padding-top: 8px; @@ -273,7 +303,8 @@ body.touch .umb-tree { .umb-tree-icon { vertical-align: middle; margin: 0 13px 0 0; - color: @gray-1; + //color: @gray-1; + color: @ui-option-type; font-size: 20px; &.blue { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less index de746090b9..21f4a7bda8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -70,13 +70,13 @@ } .umb-avatar--primary { - background-color: @turquoise-l1; - color: @white; + background-color: @pinkLight; + color: @blueExtraDark; } .umb-avatar--secondary { - background-color: @purple-l3; - color: @white; + background-color: @pinkLight; + color: @blueExtraDark; } .umb-avatar--success { @@ -114,4 +114,4 @@ a.umb-avatar-btn .umb-avatar span { font-size: 50px; } -/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: #F3F3F5; border: 2px dashed #A2A1A6; color: #A2A1A6;*/ \ No newline at end of file +/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: #F3F3F5; border: 2px dashed #A2A1A6; color: #A2A1A6;*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less index 0e3bb35ffa..9a18bef2de 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less @@ -10,27 +10,27 @@ // Colors .umb-badge--primary { - background-color: @turquoise; + background-color: @blueDark; color: @white; } .umb-badge--secondary { - background-color: @purple; + background-color: @blueExtraDark; color: @white; } .umb-badge--gray { - background-color: @gray-8; - color: @gray-4; + background-color: @sand-2; + color: @blueDark; } .umb-badge--danger { - background-color: @red-l1; + background-color: @red; color: @white; } .umb-badge--warning { - background-color: @yellow-d1; + background-color: @orange; color: @white; } @@ -68,4 +68,4 @@ .umb-badge--xl { font-size: 20px; padding: 6px 8px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less index 18275631dd..b6cdc0e8d9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less @@ -19,6 +19,13 @@ cursor: pointer; text-align: center; justify-content: center; + + color:@ui-action-type; + &:hover { + color:@ui-action-type-hover; + border-color:@ui-action-type-hover; + text-decoration:none; + } } .umb-child-selector__children-container { @@ -44,11 +51,11 @@ .umb-child-selector__child-name { font-size: 14px; } - +/* .umb-child-selector__child-name.-blue { color: @turquoise-d1; } - +*/ .umb-child-selector__child-actions { flex: 0 0 50px; text-align: right; 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 6dc2dd6ff3..8cf64e183c 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 @@ -57,10 +57,6 @@ .umb-color-box__label { background: @white; font-size: 14px; - display: flex; - flex-flow: column wrap; - flex: 1 0 100%; - justify-content: flex-end; padding: 1px 5px; min-height: 45px; max-width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less index d8b83af67a..c1f099c2a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation.less @@ -10,7 +10,7 @@ cursor: pointer; display: block; padding: 4px 10px 0 10px; - border-bottom: 2px solid transparent; + //border-bottom: 4px solid transparent; min-width: 70px; border-right: 1px solid @gray-9; box-sizing: border-box; @@ -20,6 +20,25 @@ justify-content: center; height: @editorHeaderHeight; position: relative; + + color: @ui-active-type; + + &:hover { + color: @ui-active-type-hover !important; + } + + &::after { + content: ""; + height: 0px; + left: 8px; + right: 8px; + background-color: @ui-light-active-border; + position: absolute; + bottom: 0; + border-radius: 3px 3px 0 0; + opacity: 0; + transition: all .2s linear; + } } .umb-sub-views-nav-item:focus { @@ -32,8 +51,16 @@ } .umb-sub-views-nav-item.is-active { - color: @turquoise-d1; - border-bottom-color: @turquoise-d1; + //color: @ui-active; + //border-bottom-color: @ui-active; + + //background-color: rgba(@ui-active, 0.25); + color: @ui-light-active-type; + //border-bottom-color: @ui-active; + &::after { + opacity: 1; + height: 4px; + } } .show-validation .umb-sub-views-nav-item.-has-error { @@ -53,7 +80,7 @@ right: 6px; min-width: 16px; color: @white; - background-color: @turquoise-d1; + background-color: @ui-active-type; border: 2px solid @white; border-radius: 50%; font-size: 10px; @@ -63,7 +90,7 @@ display: block; &.-type-alert { - background-color: @red-l1; + background-color: @red; } &.-type-warning { background-color: @yellow-d2; @@ -98,7 +125,7 @@ // make dots green the an item is active .umb-sub-views-nav-item.is-active .umb-sub-views-nav-item__more i { - background-color: @turquoise-d1; + background-color: @ui-active; } .umb-sub-views-nav__dropdown.umb-sub-views-nav__dropdown { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less index b7c58ad3cf..63190f0ed3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-file-dropzone.less @@ -43,14 +43,14 @@ // file select link .file-select { font-size: 15px; - color: @turquoise-d1; + color: @ui-action-disgrete-type; cursor: pointer; margin-top: 10px; &:hover { - color: @turquoise-d1; - text-decoration: underline; + color: @ui-action-disgrete-type-hover; + text-decoration: none; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less index 1848d55d96..8cdcc8b877 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-flatpickr.less @@ -13,9 +13,9 @@ span.flatpickr-day:hover { } span.flatpickr-day.selected { - background-color: @turquoise; + background-color: @ui-selected; } span.flatpickr-day.selected:hover { - background-color: @turquoise-d1; -} \ No newline at end of file + background-color: @ui-selected-hover; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index d91f24cbca..f5ca1f8438 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -14,7 +14,7 @@ padding: 10px 20px; box-sizing: border-box; flex: 1 1 200px; - border: 1px solid transparent; + border: 2px dashed transparent; transition: border 0.2s; position: relative; justify-content: space-between; @@ -22,8 +22,24 @@ user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,0.16); border-radius: 3px; + + transition: box-shadow 150ms ease-in-out; } +.umb-folder-grid__folder.-selected { + box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); + + //background: @ui-selected; + color:@ui-selected-type; + border-color:@ui-selected-border; + + &:hover { + color:@ui-selected-type-hover; + border-color:@ui-selected-border-hover; + } +} + + .umb-folder-grid__folder:focus, .umb-folder-grid__folder:active { text-decoration: none; @@ -31,8 +47,6 @@ .umb-folder-grid__folder:hover { text-decoration: none; - box-shadow: 0 3px 6px 0 rgba(0,0,0,0.16); - transition: box-shadow 150ms ease-in-out; } .umb-folder-grid__folder-description { @@ -57,7 +71,7 @@ .umb-folder-grid__folder-name:hover { text-decoration: underline; } - +/* .umb-folder-grid__action { opacity: 0; border: 2px solid @white; @@ -78,3 +92,4 @@ opacity: 1; background: @green; } +*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less index 7a0b013e68..e25349f555 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid-selector.less @@ -28,6 +28,13 @@ border: 1px dashed @gray-8; background: none; cursor: pointer; + + color: @ui-option-type; + &:hover { + color: @ui-option-type-hover; + border-color: @ui-option-type-hover; + text-decoration: none; + } } .umb-grid-selector__item-content { @@ -48,7 +55,7 @@ } .umb-grid-selector__item-label.-blue { - color: @turquoise-d1; + color: @ui-option-type; } .umb-grid-selector__item-remove { @@ -56,6 +63,10 @@ top: 5px; right: 5px; cursor: pointer; + color: @ui-option-type; + &:hover { + color: @ui-option-type-hover; + } } .umb-grid-selector__item-default-label { @@ -64,9 +75,15 @@ } .umb-grid-selector__item-default-label.-blue { - color: @turquoise-d1; + color: @ui-option-type; + &:hover { + color: @ui-option-type-hover; + } } .umb-grid-selector__item-add { - color: @turquoise-d1; + color: @ui-option-type; + &:hover { + color: @ui-option-type-hover; + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index be6729bf56..33df17c754 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -17,7 +17,7 @@ } .umb-group-builder__group.-active { - border-color: @turquoise; + border-color: @ui-active; } .umb-group-builder__group.-inherited { @@ -33,10 +33,15 @@ align-items: center; cursor: pointer; border: 1px dashed @gray-7; - color: @turquoise-d1; + color: @ui-action-type; font-weight: bold; position: relative; box-shadow: none; + &:hover { + color:@ui-action-type-hover; + text-decoration: none; + border-color: @ui-active-type-hover; + } } .umb-group-builder__group.-sortable { @@ -61,11 +66,12 @@ position: relative; margin-left: auto; font-size: 18px; + color: @ui-icon; } .umb-group-builder__group-remove:hover { cursor: pointer; - color: @turquoise; + color: @ui-icon-hover; } .umb-group-builder__group-title-wrapper { @@ -296,10 +302,11 @@ input.umb-group-builder__group-title-input:disabled:hover { font-size: 18px; position: relative; cursor: pointer; + color: @ui-icon; } .umb-group-builder__property-action:hover { - color: @turquoise; + color: @ui-icon-hover; } .umb-group-builder__property-tags { @@ -409,20 +416,21 @@ input.umb-group-builder__group-sort-value { } .editor-placeholder { - border: 1px dashed @gray-8; + border: 1px dashed @ui-action-border; width: 100%; height: 80px; line-height: 80px; text-align: center; display: block; border-radius: 5px; - color: @gray-3; font-weight: bold; font-size: 14px; - color: @turquoise-d1; - + color: @ui-action-type; + &:hover { text-decoration: none; + color:@ui-action-type-hover; + border-color:@ui-action-border-hover; } } @@ -493,4 +501,4 @@ input.umb-group-builder__group-sort-value { label.checkbox.no-indent { width: 100%; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 6b03d80214..34070256ce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -34,7 +34,7 @@ .umb-iconpicker-item.-selected { background: @gray-10; - border: solid 1px @turquoise-d1; + border: solid 1px @ui-active; border-radius: @baseBorderRadius; box-sizing: border-box; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less index b0f9a7db3d..a87e7084fb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less @@ -4,23 +4,24 @@ } .umb-insert-code-box { - border: 2px solid @gray-10; + border: 1px solid @gray-10; padding: 15px 20px; margin-bottom: 10px; border-radius: 3px; + cursor: pointer; } -.umb-insert-code-box:hover, +.umb-insert-code-box:hover, .umb-insert-code-box.-selected { - border-color: @turquoise; - cursor: pointer; + background-color: @ui-option-hover; + color: @ui-action-type-hover; + //border-color: @ui-action-border-hover; } .umb-insert-code-box__title { font-size: 15px; margin-bottom: 5px; font-weight: bold; - color: @black; } .umb-insert-code-box__description { @@ -31,7 +32,7 @@ .umb-insert-code-box__check { width: 18px; height: 18px; - background: @gray-10; + background: @gray-10;x border-radius: 50%; display: flex; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less index 4fb20f5c68..733836d85f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less @@ -34,11 +34,12 @@ .umb-list-view-settings__create-new { font-size: 13px; - color: @turquoise-d1; + color: @ui-action-type; } .umb-list-view-settings__create-new:hover { - color: @turquoise-d1; + color: @ui-action-type-hover; + border-color: @ui-action-type-hover; } .umb-list-view-settings__remove-new { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 3356c1de68..8185040f23 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -23,16 +23,30 @@ position: relative; overflow: hidden; cursor: pointer; - + + border: 2px dashed transparent; box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); - - transition: box-shadow 100ms; + + transition: box-shadow 150ms ease-in-out; } .umb-media-grid__item.-file { background-color: @white; } +.umb-media-grid__item.-selected { + box-shadow: 0 2px 8px 0 darken(@ui-selected-border, 20); + + //background: @ui-selected; + color:@ui-selected-type; + border-color:@ui-selected-border; + + &:hover { + color:@ui-selected-type-hover; + border-color:@ui-selected-border-hover; + } +} + .umb-media-grid__item-file-icon > span { color: @white; background: @gray-4; @@ -45,10 +59,6 @@ position: relative; } -.umb-media-grid__item.-selected { - box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); -} - .umb-media-grid__item:hover { text-decoration: none; } @@ -90,12 +100,13 @@ box-sizing: border-box; font-size: 12px; overflow: hidden; - color: @white; + color: @black; white-space: nowrap; - background: @purple; + border-top:1px solid @gray-9; + background: @white; transition: opacity 150ms; } - +/* .umb-media-grid__item.-file .umb-media-grid__item-overlay { opacity: 1; color: @gray-4; @@ -105,9 +116,9 @@ .umb-media-grid__item.-file:hover .umb-media-grid__item-overlay, .umb-media-grid__item.-file.-selected .umb-media-grid__item-overlay { color: @white; - background: @purple; + background: @blueExtraDark; } - +*/ .umb-media-grid__info { margin-right: 5px; } @@ -134,7 +145,7 @@ font-size: 40px !important; transform: translate(-50%,-50%); } - +/* .umb-media-grid__checkmark { position: absolute; z-index: 2; @@ -152,7 +163,7 @@ color: @white; transition: background 100ms; } - +*/ .umb-media-grid__edit { position: absolute; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 07bc32d79a..5b27206fc2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -71,12 +71,12 @@ margin-right: 5px; font-size: 13px; font-weight: bold; - color: @gray-5; + color: @ui-action-type; cursor: pointer; } .umb-node-preview__action:hover { - color: @turquoise; + color: @ui-action-type-hover; text-decoration: none; opacity: 1; } @@ -90,14 +90,16 @@ align-items: center; justify-content: center; border: 1px dashed @gray-7; - color: @turquoise-d1; + color: @ui-action-type; font-weight: bold; padding: 5px 15px; box-sizing: border-box; } .umb-node-preview-add:hover { - color: @turquoise-d1; + color: @ui-action-type-hover; + border-color: @ui-action-type-hover; + text-decoration: none; } .umb-editor-wrapper .umb-node-preview-add { @@ -112,4 +114,4 @@ .umb-node-preview-add { max-width: none; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less index 99759fcee7..d229bc81b6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less @@ -13,7 +13,7 @@ position: relative; width: 500px; height: 300px; - border: 2px dashed @gray-8; + border: 2px dashed @ui-action-border; border-radius: 3px; background: @white; display: flex; @@ -22,10 +22,9 @@ align-items: center; margin-bottom: 30px; transition: 100ms box-shadow ease, 100ms border ease; - - &:hover, + &.drag-over { - border-color: @turquoise; + border-color: @ui-action-border-hover; border-style: solid; box-shadow: 0 3px 8px rgba(0,0,0, .1); transition: 100ms box-shadow ease, 100ms border ease; @@ -34,18 +33,19 @@ .umb-upload-local__dropzone i { display: block; - color: @gray-8; + color: @ui-action-type; font-size: 110px; line-height: 1; } .umb-upload-local__select-file { font-weight: bold; - color: @turquoise-d1; + color: @ui-action-type; cursor: pointer; &:hover { text-decoration: underline; + color: @ui-action-type-hover; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less index 348d7bb5db..5e98075fc2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-progress-circle.less @@ -17,7 +17,7 @@ } .umb-progress-circle__highlight--primary { - stroke: @turquoise; + stroke: @blue; } .umb-progress-circle__highlight--secondary { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index 9da54f0bf9..f22d8b3a16 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -14,24 +14,54 @@ } .umb-tab > a { + + display: flex; + justify-content: center; + align-items: center; + position: relative; + cursor: pointer; - border-bottom: 2px solid transparent; - color: @gray-3; + //border-bottom: 4px solid transparent; + color: @ui-light-type; padding: 5px 20px 15px 20px; transition: color 150ms ease-in-out; + &:focus { + color: @ui-light-type-hover; + } + &:hover { + color: @ui-light-type-hover; + text-decoration: none; + } + &::after { + content: ""; + height: 0px; + left: 14px; + right: 14px; + background-color: @ui-light-active-border; + position: absolute; + bottom: 0; + border-radius: 3px 3px 0 0; + opacity: 0; + transition: all .2s linear; + } } -.umb-tab > a:hover, -.umb-tab > a:focus { - color: @black; - text-decoration: none; -} -.umb-tab--active > a, -.umb-tab--active > a:hover, -.umb-tab--active > a:focus { - color: @black; - border-bottom-color: @turquoise; +.umb-tab--active > a { + color: @ui-light-active-type; + //border-bottom-color: @ui-active; + /* + &:focus { + border-bottom-color: @ui-active; + } + &:hover { + border-bottom-color: @ui-active-type-hover; + } + */ + &::after { + opacity: 1; + height: 4px; + } } .show-validation .umb-tab--error > a, @@ -72,4 +102,4 @@ display: inline-block; margin: 0 5px 0 0; opacity: .6; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards.less b/src/Umbraco.Web.UI.Client/src/less/dashboards.less index cc13ad31fd..0ddef2e8b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards.less @@ -133,4 +133,4 @@ } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index f9bb860b5e..7b2afad20b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -201,7 +201,7 @@ input[type="color"], margin-bottom: @baseLineHeight / 2; font-size: @baseFontSize; line-height: @baseLineHeight; - color: @gray-3; + color: @gray-2; .border-radius(@inputBorderRadius); vertical-align: middle; box-sizing: border-box; @@ -243,11 +243,12 @@ input[type="color"], .uneditable-input { background-color: @inputBackground; border: 1px solid @inputBorder; + .transition(~"border linear .2s, box-shadow linear .2s"); // Focus state &:focus { - border-color: none; + border-color: @inputBorderFocus; outline: 0; outline: none \9; /* IE6-9 */ } diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 2271703540..c0cb609afb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -3,14 +3,6 @@ .umb-listview{width: auto !important;} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { - color: @black; - background: @gray-10; -} - .umb-listview table { border: 1px solid @gray-8; } @@ -254,8 +246,8 @@ .list-view-add-layout { margin-top: 10px; - color: @turquoise-d1; - border: 1px dashed @gray-8; + color: @ui-action-disgrete-type; + border: 1px dashed @ui-action-disgrete-border; display: flex; align-items: center; justify-content: center; @@ -265,4 +257,6 @@ .list-view-add-layout:hover { text-decoration: none; + color: @ui-action-disgrete-type-hover; + border-color: @ui-action-disgrete-border-hover; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 12a13e11ed..b363170ebc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -120,7 +120,7 @@ h5.-black { margin: 20px; } .umb-control-group { - border-bottom: 1px solid @gray-10; + border-bottom: 1px solid @gray-11; padding-bottom: 20px; margin-bottom: 15px !important; } @@ -495,7 +495,7 @@ table thead a { // ------------------------ .umb-loader{ -background-color: @turquoise-d1; +background-color: @blue; margin-top:0; margin-left:-100%; -moz-animation-name:bounce_loadingProgressG; @@ -519,7 +519,7 @@ animation-duration:1s; animation-iteration-count:infinite; animation-timing-function:linear; width:100%; -height:1px; +height:2px; } @@ -671,4 +671,3 @@ input[type=checkbox]:checked + .input-label--small { width: 1px !important; overflow: hidden; } - diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index 5b4211f177..b67fd1dd25 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -515,28 +515,39 @@ // Button backgrounds // ------------------ -.buttonBackground(@startColor, @endColor, @textColor: #fff) { +.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: #fff, @textColorHover: @textColor) { // gradientBar will set the background to a pleasing blend of these, to support IE<=9 - .gradientBar(@startColor, @endColor, @textColor); - *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + //.gradientBar(@startColor, @endColor, @textColor); + + color: @textColor; + border-color: @startColor @startColor darken(@startColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); + + background-color: @startColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ .reset-filter(); - - // button states - &:hover, &:focus, &:active { - background-color: darken(@startColor, 2%); + + .caret { + border-top-color: @textColor; + border-bottom-color: @textColor; } // in these cases the gradient won't cover the background, so we override - &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { - color: @textColor; - *background-color: darken(@endColor, 5%); + &:hover, &:focus, &:active, &.active { + color: @textColorHover; + background-color: @hoverColor; } - + + &.disabled, &[disabled] { + color: @white; + background-color: @sand-2; + } + /* // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves &:active, &.active { background-color: darken(@endColor, 10%) e("\9"); } + */ } // Navbar vertical align diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index d9656f194b..0101113670 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -233,6 +233,12 @@ .dropdown-menu > li > a { padding: 8px 20px; + color: @ui-option-type; +} + +.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { + color: @ui-option-type-hover; + background: @ui-option-hover; } .nav-tabs .dropdown-menu { 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 3ec8c383da..8d1b70c35d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -230,21 +230,33 @@ // // Media picker // -------------------------------------------------- + +.umb-mediapicker > div { + border: 1px solid @inputBorder; +} +.umb-mediapicker-single > div { + width:144px; +} +.umb-mediapicker-multi > div { + width:100%; +} + + .umb-mediapicker .add-link { display: flex; justify-content:center; align-items:center; - width: 120px; + //width: 120px; text-align: center; - color: @gray-8; - border: 2px @gray-8 dashed; + color: @gray-9; + border: 2px @gray-9 dashed; text-decoration: none; transition: all 150ms ease-in-out; &:hover { - color: @turquoise-d1; - border-color: @turquoise; + color: @ui-action-disgrete-type-hover; + border-color: @ui-action-disgrete-type-hover; } } @@ -283,7 +295,9 @@ } .umb-mediapicker .add-link-square { - height: 120px; + height: 100px; + width: 100px; + margin: 10px; } @@ -330,19 +344,28 @@ justify-content: center; align-items: center; flex-wrap: wrap; - padding: 2px; + padding: 5px; margin: 5px; background: @white; - border: 1px solid @gray-10; + //border: 1px solid @gray-10; max-width: 100%; } +.umb-mediapicker { + + .umb-sortable-thumbnails li { + border:none; + } + +} + .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0 0 5px 5px; - padding: 5px; } +/*.umb-mediapicker .umb-sortable-thumbnails li.add-wrapper { + padding: 0px; +}*/ .umb-sortable-thumbnails li:hover a { display: flex; @@ -445,6 +468,9 @@ align-items: center; margin-left: 5px; text-decoration: none; + + //border-color: @inputBorder; + .box-shadow(0 1px 2px rgba(0,0,0,0.25)); } .umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red { @@ -819,16 +845,16 @@ // Tags // -------------------------------------------------- .umb-tags{ - border: @gray-10 solid 1px; + border: @inputBorder solid 1px; padding: 10px; font-size: 13px; text-shadow: none; - .tag{ + .tag { cursor: default; margin: 7px; padding: 10px; - background: @turquoise; + background: @blueDark; position: relative; .icon-trash{ diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 4d3a680523..9aae9840d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -42,7 +42,6 @@ } .umb-rte .mce-tinymce .mce-edit-area { - border: 1px solid @gray-8 !important; border-radius: 0px !important; } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index ac41f69998..cf362a67f0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -28,7 +28,7 @@ ul.sections>li>a { } ul.sections>li>a .section__name { - opacity: 0.6; + opacity: 0.8; transition: opacity .1s linear; } @@ -36,7 +36,7 @@ ul.sections>li>a::after { content: ""; height: 4px; width: 100%; - background-color: @turquoise; + background-color: @pinkLight; position: absolute; bottom: -4px; border-radius: 3px 3px 0 0; @@ -45,6 +45,9 @@ ul.sections>li>a::after { transition: all .2s linear; } +ul.sections>li.current>a { + color:@pinkLight; +} ul.sections>li.current>a::after { opacity: 1; bottom: 0; @@ -54,6 +57,7 @@ ul.sections>li.current>a .section__name, ul.sections>li>a:hover .section__name, ul.sections>li>a:focus .section__name { opacity: 1; + -webkit-font-smoothing: subpixel-antialiased; } @@ -93,7 +97,7 @@ ul.sections-tray>li>a::after { content: ""; width: 4px; height: 100%; - background-color: @turquoise; + background-color: @ui-active; position: absolute; border-radius: 0 3px 3px 0; opacity: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 513c645365..8add7c9002 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -21,20 +21,6 @@ @white: #FFF; @grayIcon: #9E9E9E; -// Accent colors -// ------------------------- - - - -@blue: #2E8AEA; -@blueDark: #0064CD; -@blueLight: #ADD8E6; -@green: #46A546; -@red: #9D261D; -@yellow: #FFC40D; -@orange: #DF7F48; -@pink: #C3325F; - // Colors // ------------------------- @@ -56,22 +42,22 @@ // UI Colors @red-d1: #F02E28; -@red: #FE3E39; -@red-l1: #FE6561; +@red: #D42054;// updated 2019 +@red-l1: #e22c60;// updated 2019 @red-l2: #FE8B88; @red-l3: #FFB2B0; @red-washed: #FFECEB; @yellow-d2: #F0AC00; @yellow-d1: #FFC011; -@yellow: #FFCE38; +@yellow: #fad634;// updated 2019 @yellow-l1: #FFD861; @yellow-l2: #FFE28A; @yellow-l3: #FFECB0; @yellow-washed: #FFFAEB; @green-d1: #1FB572; -@green: #35C786; +@green: #2bc37c;// updated 2019 @green-l1: #4ECF95; @green-l2: #79E1B2; @green-l3: #A6F0CF; @@ -88,6 +74,12 @@ @gray-8: #D8D7D9; @gray-9: #E9E9EB; @gray-10: #F3F3F5; +@gray-11: #F6F6F7; + +@sand-1: hsl(22, 33%, 93%);// added 2019 +@sand-2: hsl(22, 34%, 88%);// added 2019 +@sand-5: hsl(22, 31%, 93%);// added 2019 +@sand-7: hsl(22, 26%, 97%);// added 2019 // Additional Icon Colours @@ -109,6 +101,89 @@ @indigoIcon: #3F51B5; +// Brand colors +// ------------------------- + +//@blueLight: #4f89de; +@blue: #2E8AEA; +@blueMid: #2152A3;// updated 2019 +@blueDark: #3544b1;// updated 2019 +@blueExtraDark: #1b264f;// added 2019 +@blueLight: #ADD8E6; +@blueNight: #162335;// added 2019 +@orange: #f79c37;// updated 2019 +@pink: #D93F4C;// #C3325F;// update 2019 +@pinkLight: #f5c1bc;// added 2019 +@pinkRedLight: #ff8a89;// added 2019 +@brown: #9d8057;// added 2019 +@brownLight: #e4e0dd;// added 2019 +@brownGrayLight: #f3f2f1;// added 2019 +@orange: #ff9412;// added 2019 + +//@u-greyLight: #f2ebe6;// added 2019 +@u-white: #f9f7f4;// added 2019 +@u-black: black;// added 2019 + + +// UI colors +// ------------------------- + +@ui-option-type: @blueExtraDark; +@ui-option-hover: @sand-7; +@ui-option-type-hover: @blueMid; + +//@ui-active: #346ab3; +@ui-active: @pinkLight; +@ui-active-type: @blueExtraDark; +@ui-active-type-hover: @blueMid; + +@ui-selected: @sand-1; +@ui-selected-hover: ligthen(@sand-1, 10); +@ui-selected-type: @blueExtraDark; +@ui-selected-type-hover: @blueMid; +@ui-selected-border: @pinkLight; +@ui-selected-border-hover: @pinkLight; + +@ui-light-border: @pinkLight; +@ui-light-type: @gray-4; +@ui-light-type-hover: @blueMid; + +@ui-light-active-border: @pinkLight; +@ui-light-active-type: @blueExtraDark; +@ui-light-active-type-hover: @blueMid; + + +@ui-action: white; +@ui-action-hover: @sand-7; +@ui-action-type: @blueExtraDark; +@ui-action-type-hover: @blueMid; +@ui-action-border: @blueExtraDark; +@ui-action-border-hover: @blueMid; + +@ui-action-disgrete: white; +@ui-action-disgrete-hover: @sand-7; +@ui-action-disgrete-type: @gray-4; +@ui-action-disgrete-type-hover: @blueMid; +@ui-action-disgrete-border: @gray-4; +@ui-action-disgrete-border-hover: @blueMid; + +@type-white: @white; +@type-black: @blueNight; + +@ui-btn: @blueExtraDark; +@ui-btn-hover: @blueMid; +@ui-btn-type: @white; + +@ui-btn-positive: @green; +@ui-btn-positive-hover: @green-l1; +@ui-btn-positive-type: @white; + +@ui-btn-negative: @red; +@ui-btn-negative-hover: @red; + +@ui-icon: @blueNight; +@ui-icon-hover: @blueMid; + @@ -130,15 +205,12 @@ .btn-color-black {background-color: @black;} .color-black i { color: @black;} - .btn-color-blue-grey {background-color: @blueGrey;} .color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} - .btn-color-grey{background-color: @grayIcon;} .color-grey, .color-grey i { color: @grayIcon !important; } - .btn-color-brown{background-color: @brownIcon;} .color-brown, .color-brown i { color: @brownIcon !important; } @@ -193,7 +265,7 @@ // ------------------------- @appHeaderHeight: 55px; @bodyBackground: @gray-10; -@textColor: @gray-2; +@textColor: #000; @editorHeaderHeight: 70px; @editorFooterHeight: 50px; @@ -253,19 +325,27 @@ @btnBackgroundHighlight: @gray-9; @btnBorder: @gray-9; -@btnPrimaryBackground: @green; -@btnPrimaryBackgroundHighlight: @green; +@btnPrimaryBackground: @ui-btn-positive; +@btnPrimaryBackgroundHighlight: @ui-btn-positive-hover; -@btnInfoBackground: @purple-l1; -@btnInfoBackgroundHighlight: @purple-l1; +@btnInfoType: @ui-btn-type;// updated 2019 +@btnInfoTypeHover: @ui-btn-type;// updated 2019 +@btnInfoBackground: @ui-btn;// updated 2019 +@btnInfoBackgroundHighlight: @ui-btn-hover;// updated 2019 -@btnSuccessBackground: @green; -@btnSuccessBackgroundHighlight: @green; +@btnWhiteType: @blueExtraDark;// updated 2019 +@btnWhiteTypeHover: @ui-action-type-hover;// updated 2019 +@btnWhiteBackground: @white;// updated 2019 +@btnWhiteBackgroundHighlight: @white;// updated 2019 -@btnWarningBackground: @red-l1; -@btnWarningBackgroundHighlight: @red-l1; +@btnSuccessType: @ui-btn-positive-type;// updated 2019 +@btnSuccessBackground: @ui-btn-positive;// updated 2019 +@btnSuccessBackgroundHighlight: @ui-btn-positive-hover;// updated 2019 -@btnDangerBackground: @red-l1; +@btnWarningBackground: @orange; +@btnWarningBackgroundHighlight: lighten(@orange, 10%); + +@btnDangerBackground: @red; @btnDangerBackgroundHighlight: @red-l1; @btnInverseBackground: @gray-2; @@ -278,7 +358,8 @@ // Forms // ------------------------- @inputBackground: @white; -@inputBorder: @gray-7; +@inputBorder: @gray-8; +@inputBorderFocus: @gray-7; @inputBorderRadius: 0; @inputDisabledBackground: @gray-10; @formActionsBackground: @gray-9; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 4a43c340db..5883313753 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -28,8 +28,6 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.treeAlias = $scope.model.treeAlias; vm.multiPicker = $scope.model.multiPicker; vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true; - // if you need to load a not initialized tree set this value to false - default is true - vm.onlyInitialized = $scope.model.onlyInitialized; vm.searchInfo = { searchFromId: $scope.model.startNodeId, searchFromName: null, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index da81d48c78..c592b4ec3b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -56,7 +56,6 @@ hideheader="{{vm.hideHeader}}" hideoptions="true" isdialog="true" - only-initialized="{{vm.onlyInitialized}}" customtreeparams="{{vm.customTreeParams}}" enablelistviewsearch="true" enablelistviewexpand="true" diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js index 3147989821..5ed87f073b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.controller.js @@ -2,17 +2,18 @@ angular.module("umbraco") .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, dashboardResource, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) { $scope.history = historyService.getCurrent(); - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; + //$scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; $scope.showPasswordFields = false; $scope.changePasswordButtonState = "init"; - $scope.model.subtitle = "Umbraco version" + " " + $scope.version; - + $scope.model.title = "user.name"; + //$scope.model.subtitle = "Umbraco version" + " " + $scope.version; + /* if(!$scope.model.title) { localizationService.localize("general_user").then(function(value){ $scope.model.title = value; }); } - + */ $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; var evts = []; @@ -45,7 +46,7 @@ angular.module("umbraco") $location.path(link); $scope.model.close(); }; - + /* //Manually update the remaining timeout seconds function updateTimeout() { $timeout(function () { @@ -58,7 +59,7 @@ angular.module("umbraco") }, 1000, false); // 1 second, do NOT execute a global digest } - + */ function updateUserInfo() { //get the user userService.getCurrentUser().then(function (user) { @@ -68,7 +69,7 @@ angular.module("umbraco") $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; //set the timer - updateTimeout(); + //updateTimeout(); authResource.getCurrentUserLinkedLogins().then(function(logins) { //reset all to be un-linked diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 8021d66a43..404b4d8dd2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -4,18 +4,18 @@
-

+ @@ -25,6 +25,7 @@ alias="changePassword" type="button" action="togglePasswordFields()" + button-style="action" label="Change password" label-key="general_changePassword" button-style="success"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatecontent/nodename/nodename.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatecontent/nodename/nodename.html index 53badd7843..b048b245c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatecontent/nodename/nodename.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatecontent/nodename/nodename.html @@ -19,7 +19,7 @@

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/doctypename/doctypename.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/doctypename/doctypename.html index a0b4b529e8..3ae8943560 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/doctypename/doctypename.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/doctypename/doctypename.html @@ -19,7 +19,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/propertyname/propertyname.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/propertyname/propertyname.html index 5748f65a95..2c255706c8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/propertyname/propertyname.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/propertyname/propertyname.html @@ -19,7 +19,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/tabname/tabname.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/tabname/tabname.html index baa0d3da9a..0dfa084878 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/tabname/tabname.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintrocreatedoctype/tabname/tabname.html @@ -19,7 +19,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/foldername/foldername.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/foldername/foldername.html index 1a9bd41226..063f332e1d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/foldername/foldername.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/foldername/foldername.html @@ -19,7 +19,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/uploadimages/uploadimages.html b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/uploadimages/uploadimages.html index e7e8750823..0e13b9b645 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/uploadimages/uploadimages.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/tours/umbintromediasection/uploadimages/uploadimages.html @@ -19,7 +19,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index c5b4f69cef..275c814761 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -38,7 +38,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html index b801d63cca..b756d23b50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html @@ -31,11 +31,11 @@
- +
- +
@@ -55,7 +55,7 @@ - + @@ -76,7 +76,7 @@

Please go back and start the tour again.

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html index adde101baa..e97eabf17f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html @@ -6,7 +6,7 @@ - - @@ -36,16 +36,16 @@ type="button"> - - - - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index dad9dc5ba9..4861aac8a4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -13,7 +13,7 @@
- -
+
-
-
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 25925f42ed..1023953ce0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -23,7 +23,7 @@
-
-
-
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html index 54a33a705f..c621670462 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -2,7 +2,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html index 40b0e181bf..aac2e2665b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-overlay.html @@ -67,7 +67,7 @@
-

{{title}}

+

{{dialogTitle}}

-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index a18319b4f2..91d461668f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,9 +1,9 @@ 
-
- \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html index ebe829ea52..70839f2625 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-confirm.html @@ -1,10 +1,10 @@

{{caption}}

- +
-
+
Cancel - Ok -
+ Ok +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html index df5eafbbca..6796b7d64b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-folder-grid.html @@ -11,7 +11,7 @@
{{ folder.name }}
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html index c0490a1707..e1d0182826 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html @@ -9,7 +9,7 @@ alias="compositions" ng-if="compositions !== false" type="button" - button-style="outline" + button-style="action" label-key="contentTypeEditor_compositions" icon="icon-merge" action="openCompositionsDialog()" @@ -21,7 +21,7 @@ alias="reorder" ng-if="sorting !== false" type="button" - button-style="outline" + button-style="action" label-key="{{sortingButtonKey}}" icon="icon-navigation" action="toggleSortingMode();" diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 360c6b2bb7..da94729562 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,7 +1,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/delete.html b/src/Umbraco.Web.UI.Client/src/views/content/delete.html index 8e85bfeaeb..047776de54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/delete.html @@ -5,11 +5,11 @@ Are you sure you want to delete {{currentNode.name}} ?

-
+
This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/notify.html b/src/Umbraco.Web.UI.Client/src/views/content/notify.html index 8e2f860661..43f14d519f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/notify.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/notify.html @@ -42,7 +42,7 @@ type="button" action="vm.save(vm.notifyOptions)" state="vm.saveState" - button-style="success"> + button-style="action">
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/protect.html b/src/Umbraco.Web.UI.Client/src/views/content/protect.html index ae4a15e8c1..77584dac12 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/protect.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/protect.html @@ -127,7 +127,7 @@
@@ -144,7 +144,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rights.html b/src/Umbraco.Web.UI.Client/src/views/content/rights.html index 35f3d34260..31c7c628c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/rights.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/rights.html @@ -75,7 +75,7 @@ label-key="prompt_stay" action="vm.stay()" type="button" - button-style="success"> + button-style="action">
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index be1219f052..3a72b0b40d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -42,7 +42,7 @@ @@ -50,14 +50,13 @@ ng-if="page.listViewPath" type="link" href="#{{page.listViewPath}}" - label="Return to list" label-key="buttons_returnToList"> diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 19932887fd..4868bf0c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -40,7 +40,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, $scope.page.menu.currentNode = null; //the editors affiliated node $scope.page.listViewPath = null; $scope.page.saveButtonState = "init"; - $scope.page.submitButtonLabel = "Save"; + $scope.page.submitButtonLabelKey = "buttons_save"; /** Syncs the content item to it's tree node - this occurs on first load and after saving */ function syncTreeNode(content, path, initialLoad) { @@ -103,7 +103,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, // setup infinite mode if(infiniteMode) { - $scope.page.submitButtonLabel = "Save and Close"; + $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/edit.controller.js index 282fd0472c..b6026edf03 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/edit.controller.js @@ -198,7 +198,7 @@ treeAlias: "files", entityType: "file", multiPicker: true, - onlyInitialized: false, + isDialog: true, select: function(node) { node.selected = !node.selected; 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 5c34040a8d..89e8075d33 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html @@ -62,7 +62,6 @@
-
{{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 3cca62b2b9..d11e240c3c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/create.html @@ -52,7 +52,6 @@
-
{{vm.createFolderError.errorMsg}}
{{vm.createFolderError.data.message}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 77576cac6a..dba6a41a7c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -5,7 +5,7 @@
- + @@ -37,7 +37,7 @@
- + Create diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 4e85668c05..d2b923b17c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -1,4 +1,4 @@ -
+

diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html index 89a6ce1f7d..2dc1f39ff8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html @@ -29,7 +29,6 @@
-
{{vm.createFolderError.errorMsg}}
{{vm.createFolderError.data.message}}
@@ -50,4 +49,4 @@
-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html index 0554c430d1..630d23fe78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/create.html @@ -35,7 +35,6 @@
-
{{vm.createFolderError.errorMsg}}
{{vm.createFolderError.data.message}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 0075342a36..4b6f77e696 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -11,7 +11,7 @@ @@ -392,7 +392,7 @@ @@ -536,7 +536,7 @@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 9dceead927..3c57fa35d8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -187,7 +187,8 @@ Oprettelse af mappen under parent med ID %0% fejlede Oprettelse af mappen under parent med navnet %0% fejlede - Sletning af filen/mappen fejlede: %0% + Mappens navn må ikke indeholde ugyldige tegn. + Sletning af filen/mappen fejlede: %0% Udgivet diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 3dcbfcded2..8135591c0c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -195,6 +195,7 @@ Failed to create a folder under parent with ID %0% Failed to create a folder under parent with name %0% + The folder name cannot contain illegal characters. Failed to delete item: %0% 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 8c8a405e29..48ea15e3dc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -199,6 +199,7 @@ Failed to create a folder under parent with ID %0% Failed to create a folder under parent with name %0% + The folder name cannot contain illegal characters. Failed to delete item: %0% diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 4b210239aa..a0f8cd6062 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Configuration; using System.Linq; +using System.Runtime.Serialization; using System.Web; using System.Web.Configuration; using System.Web.Mvc; @@ -158,7 +159,7 @@ namespace Umbraco.Web.Editors }, { "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetApplicationTrees(null, null, null)) + controller => controller.GetApplicationTrees(null, null, null, TreeUse.None)) }, { "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( @@ -348,7 +349,10 @@ namespace Umbraco.Web.Editors { "umbracoPlugins", new Dictionary { - {"trees", GetTreePluginsMetaData()} + // for each tree that is [PluginController], get + // alias -> areaName + // so that routing (route.js) can look for views + { "trees", GetPluginTrees().ToArray() } } }, { @@ -389,43 +393,42 @@ namespace Umbraco.Web.Editors return defaultVals; } - private IEnumerable> GetTreePluginsMetaData() + [DataContract] + private class PluginTree { - var treeTypes = TreeControllerTypes.Value; - //get all plugin trees with their attributes - var treesWithAttributes = treeTypes.Select(x => new - { - tree = x, - attributes = - x.GetCustomAttributes(false) - }).ToArray(); - - var pluginTreesWithAttributes = treesWithAttributes - //don't resolve any tree decorated with CoreTreeAttribute - .Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false)) - //we only care about trees with the PluginControllerAttribute - .Where(x => x.attributes.Any(a => a is PluginControllerAttribute)) - .ToArray(); - - return (from p in pluginTreesWithAttributes - let treeAttr = p.attributes.OfType().Single() - let pluginAttr = p.attributes.OfType().Single() - select new Dictionary - { - {"alias", treeAttr.TreeAlias}, {"packageFolder", pluginAttr.AreaName} - }).ToArray(); + [DataMember(Name = "alias")] + public string Alias { get; set; } + [DataMember(Name = "packageFolder")] + public string PackageFolder { get; set; } } - /// - /// A lazy reference to all tree controller types - /// - /// - /// We are doing this because if we constantly resolve the tree controller types from the PluginManager it will re-scan and also re-log that - /// it's resolving which is unnecessary and annoying. - /// - private static readonly Lazy> TreeControllerTypes - = new Lazy>(() => Current.TypeLoader.GetAttributedTreeControllers().ToArray()); // TODO: inject + private IEnumerable GetPluginTrees() + { + // used to be (cached) + //var treeTypes = Current.TypeLoader.GetAttributedTreeControllers(); + // + // ie inheriting from TreeController and marked with TreeAttribute + // + // do this instead + // inheriting from TreeControllerBase and marked with TreeAttribute + var trees = Current.Factory.GetInstance(); + + foreach (var tree in trees) + { + var treeType = tree.TreeControllerType; + + // exclude anything marked with CoreTreeAttribute + var coreTree = treeType.GetCustomAttribute(false); + if (coreTree != null) continue; + + // exclude anything not marked with PluginControllerAttribute + var pluginController = treeType.GetCustomAttribute(false); + if (pluginController == null) continue; + + yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName }; + } + } /// /// Returns the server variables regarding the application state diff --git a/src/Umbraco.Web/Editors/CodeFileController.cs b/src/Umbraco.Web/Editors/CodeFileController.cs index 63b6c173b5..719f7521cb 100644 --- a/src/Umbraco.Web/Editors/CodeFileController.cs +++ b/src/Umbraco.Web/Editors/CodeFileController.cs @@ -75,11 +75,14 @@ namespace Umbraco.Web.Editors /// The name of the container/folder /// [HttpPost] - public CodeFileDisplay PostCreateContainer(string type, string parentId, string name) + public HttpResponseMessage PostCreateContainer(string type, string parentId, string name) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + if (name.ContainsAny(Path.GetInvalidPathChars())) { + return Request.CreateNotificationValidationErrorResponse(Services.TextService.Localize("codefile/createFolderIllegalChars")); + } // if the parentId is root (-1) then we just need an empty string as we are // creating the path below and we don't want -1 in the path @@ -118,11 +121,11 @@ namespace Umbraco.Web.Editors } - return new CodeFileDisplay + return Request.CreateResponse(HttpStatusCode.OK, new CodeFileDisplay { VirtualPath = virtualPath, Path = Url.GetTreePathFromFilePath(virtualPath) - }; + }); } /// diff --git a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs index f71d486f5a..4d64d30e9d 100644 --- a/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs +++ b/src/Umbraco.Web/Install/InstallSteps/ConfigureMachineKey.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Install.InstallSteps /// public override Task ExecuteAsync(bool? model) { - if (model.HasValue && model.Value == false) return null; + if (model.HasValue && model.Value == false) return Task.FromResult(null); //install the machine key var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config"); @@ -43,7 +43,7 @@ namespace Umbraco.Web.Install.InstallSteps // Update appSetting if it exists, or else create a new appSetting for the given key and value var machineKey = systemWeb.Descendants("machineKey").FirstOrDefault(); - if (machineKey != null) return null; + if (machineKey != null) return Task.FromResult(null); var generator = new MachineKeyGenerator(); var generatedSection = generator.GenerateConfigurationBlock(); diff --git a/src/Umbraco.Web/Mvc/BackOfficeArea.cs b/src/Umbraco.Web/Mvc/BackOfficeArea.cs index f501a70729..e72f2cc81a 100644 --- a/src/Umbraco.Web/Mvc/BackOfficeArea.cs +++ b/src/Umbraco.Web/Mvc/BackOfficeArea.cs @@ -44,14 +44,6 @@ namespace Umbraco.Web.Mvc id = @"[a-zA-Z]*" }, new[] {typeof (BackOfficeController).Namespace}); - - //Create the REST/web/script service routes - context.MapRoute( - "Umbraco_web_services", - _globalSettings.GetUmbracoMvcArea() + "/RestServices/{controller}/{action}/{id}", - new {controller = "SaveFileController", action = "Index", id = UrlParameter.Optional}, - //look in this namespace for controllers - new[] {"Umbraco.Web.WebServices"}); } public override string AreaName => _globalSettings.GetUmbracoMvcArea(); diff --git a/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs index 6c8b8f19ee..332f1fd2a9 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerAttribute.cs @@ -4,23 +4,27 @@ using System.Linq; namespace Umbraco.Web.Mvc { /// - /// An attribute applied to a plugin controller that requires that it is routed to its own area + /// Indicates that a controller is a plugin tree controller and should be routed to its own area. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class PluginControllerAttribute : Attribute { - public string AreaName { get; private set; } - + /// + /// Initializes a new instance of the class. + /// + /// public PluginControllerAttribute(string areaName) { - //validate this, only letters and digits allowed. - if (areaName.Any(c => !Char.IsLetterOrDigit(c))) - { - throw new FormatException("The areaName specified " + areaName + " can only contains letters and digits"); - } + // validate this, only letters and digits allowed. + if (areaName.Any(c => !char.IsLetterOrDigit(c))) + throw new FormatException($"Invalid area name \"{areaName}\": the area name can only contains letters and digits."); AreaName = areaName; } + /// + /// Gets the name of the area. + /// + public string AreaName { get; } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index b9cac88ecf..d30762f13f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (udi == null) return null; content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udi.Guid); - if (content != null) + if (content != null && content.ItemType == PublishedItemType.Content) return content; } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 0f8b75f6b1..beb22bb57e 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -98,7 +98,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters break; } - if (multiNodeTreePickerItem != null) + if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ItemType != PublishedItemType.Element) { multiNodeTreePicker.Add(multiNodeTreePickerItem); } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs index c2c604cb7f..814d499252 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters _publishedSnapshotAccessor.PublishedSnapshot.Media.GetById(preview, dto.Udi.Guid) : _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(preview, dto.Udi.Guid); - if (content == null) + if (content == null || content.ItemType == PublishedItemType.Element) { continue; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs index d130fb5952..abfde3646a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueListConfigurationEditor.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core.Services; @@ -13,7 +12,7 @@ namespace Umbraco.Web.PropertyEditors /// /// This pre-value editor is shared with editors like drop down, checkbox list, etc.... /// - internal class ValueListConfigurationEditor : ConfigurationEditor + public class ValueListConfigurationEditor : ConfigurationEditor { public ValueListConfigurationEditor(ILocalizedTextService textService) { diff --git a/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs b/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs deleted file mode 100644 index 4b2ecc28fe..0000000000 --- a/src/Umbraco.Web/Routing/WebServicesRouteConstraint.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Routing; - -namespace Umbraco.Web.Routing -{ - //public class WebServicesRouteConstraint : IRouteConstraint - //{ - // public bool Match( - // HttpContextBase httpContext, - // Route route, - // string parameterName, - // RouteValueDictionary values, - // RouteDirection routeDirection) - // { - // if (routeDirection == RouteDirection.UrlGeneration) - // { - - // } - // return true; - // } - //} -} diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs index b8a7fee2f7..3c593d383f 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComposer.cs @@ -208,14 +208,10 @@ namespace Umbraco.Web.Runtime .Add(composition.TypeLoader.GetTypes()); // register back office trees - foreach (var treeControllerType in umbracoApiControllerTypes - .Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))) - { - var attribute = treeControllerType.GetCustomAttribute(false); - if (attribute == null) continue; - var tree = new Tree(attribute.SortOrder, attribute.ApplicationAlias, attribute.TreeAlias, attribute.TreeTitle, treeControllerType, attribute.IsSingleNodeTree); - composition.WithCollectionBuilder().Trees.Add(tree); - } + // the collection builder only accepts types inheriting from TreeControllerBase + // and will filter out those that are not attributed with TreeAttribute + composition.WithCollectionBuilder() + .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); } } } diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 032782b466..8f7c6ece0b 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Search var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); if (found != null) { - dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.ApplicationAlias, appTree.TreeAlias, found); + dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.SectionAlias, appTree.TreeAlias, found); } } return dictionary; diff --git a/src/Umbraco.Web/Services/ITreeService.cs b/src/Umbraco.Web/Services/ITreeService.cs index 96787086c6..f9ed522e71 100644 --- a/src/Umbraco.Web/Services/ITreeService.cs +++ b/src/Umbraco.Web/Services/ITreeService.cs @@ -1,41 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Web.Models.ContentEditing; +using System.Collections.Generic; using Umbraco.Web.Trees; namespace Umbraco.Web.Services { + /// + /// Represents a service which manages section trees. + /// public interface ITreeService { /// - /// Gets an ApplicationTree by it's tree alias. + /// Gets a tree. /// /// The tree alias. - /// An ApplicationTree instance Tree GetByAlias(string treeAlias); /// - /// Gets all applicationTrees registered in umbraco from the umbracoAppTree table.. + /// Gets all trees. /// - /// Returns a ApplicationTree Array - IEnumerable GetAll(); - + IEnumerable GetAll(TreeUse use = TreeUse.Main); + /// - /// Gets the application tree for the applcation with the specified alias + /// Gets all trees for a section. /// - /// The application alias. - /// Returns a ApplicationTree Array - IEnumerable GetTrees(string sectionAlias); - + IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main); + /// - /// Gets the grouped application trees for the application with the specified alias + /// Gets all trees for a section, grouped. /// - /// - /// - IDictionary> GetGroupedTrees(string sectionAlias); + IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main); } - } diff --git a/src/Umbraco.Web/Services/TreeService.cs b/src/Umbraco.Web/Services/TreeService.cs index f58dce59bc..dc500f2583 100644 --- a/src/Umbraco.Web/Services/TreeService.cs +++ b/src/Umbraco.Web/Services/TreeService.cs @@ -6,65 +6,41 @@ using Umbraco.Web.Trees; namespace Umbraco.Web.Services { + /// + /// Implements . + /// internal class TreeService : ITreeService { private readonly TreeCollection _treeCollection; - private readonly Lazy>> _groupedTrees; + /// + /// Initializes a new instance of the class. + /// + /// public TreeService(TreeCollection treeCollection) { _treeCollection = treeCollection; - _groupedTrees = new Lazy>>(InitGroupedTrees); } /// - public Tree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(t => t.TreeAlias == treeAlias); + public Tree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(x => x.TreeAlias == treeAlias); /// - public IEnumerable GetAll() => _treeCollection; + public IEnumerable GetAll(TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees + => _treeCollection.Where(x => x.TreeUse.HasFlagAny(use)); /// - public IEnumerable GetTrees(string sectionAlias) - => GetAll().Where(x => x.ApplicationAlias.InvariantEquals(sectionAlias)).OrderBy(x => x.SortOrder).ToList(); + public IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees + => _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use)).OrderBy(x => x.SortOrder).ToList(); - public IDictionary> GetGroupedTrees(string sectionAlias) + /// + public IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main) { - var result = new Dictionary>(); - var foundTrees = GetTrees(sectionAlias).ToList(); - foreach(var treeGroup in _groupedTrees.Value) - { - List resultGroup = null; - foreach(var tree in foundTrees) - { - foreach(var treeAliasInGroup in treeGroup) - { - if (tree.TreeAlias != treeAliasInGroup) continue; - - 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; + return GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary( + x => x.Key ?? "", + x => (IEnumerable) x.ToArray()); } - - /// - /// 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.TreeAlias, treeGroup: x.TreeControllerType.GetCustomAttribute(false)?.TreeGroup)) - .GroupBy(x => x.treeGroup, x => x.treeAlias) - .ToList(); - return result; - } - } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 205c38b8a1..28bc464d55 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -14,11 +14,8 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Persistence; using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.Services; @@ -50,262 +47,235 @@ namespace Umbraco.Web.Trees /// /// The application to load tree for /// An optional single tree alias, if specified will only load the single tree for the request app - /// + /// + /// Tree use. /// [HttpQueryStringFilter("queryStrings")] - public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings) + public async Task GetApplicationTrees(string application, string tree, FormDataCollection querystring, TreeUse use = TreeUse.Main) { application = application.CleanForXss(); - if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); + if (string.IsNullOrEmpty(application)) + throw new HttpResponseException(HttpStatusCode.NotFound); //find all tree definitions that have the current application alias - var groupedTrees = _treeService.GetGroupedTrees(application); + var groupedTrees = _treeService.GetBySectionGrouped(application, use); var allTrees = groupedTrees.Values.SelectMany(x => x).ToList(); - if (string.IsNullOrEmpty(tree) == false || allTrees.Count == 1) + if (allTrees.Count == 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + // handle request for a specific tree / or when there is only one tree + if (!tree.IsNullOrWhiteSpace() || allTrees.Count == 1) { - var apptree = !tree.IsNullOrWhiteSpace() - ? allTrees.FirstOrDefault(x => x.TreeAlias == tree) - : allTrees.FirstOrDefault(); + var t = tree.IsNullOrWhiteSpace() + ? allTrees[0] + : allTrees.FirstOrDefault(x => x.TreeAlias == tree); - if (apptree == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (t == null) + throw new HttpResponseException(HttpStatusCode.NotFound); - var result = await GetRootForSingleAppTree( - apptree, - Constants.System.Root.ToString(CultureInfo.InvariantCulture), - queryStrings, - application); + var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, querystring); + if (treeRootNode != null) + return treeRootNode; - //this will be null if it cannot convert to a single root section - if (result != null) - { - return result; - } + throw new HttpResponseException(HttpStatusCode.NotFound); } - //Don't apply fancy grouping logic futher down, if we only have one group of items - var hasGroups = groupedTrees.Count > 1; - if (!hasGroups) + // handle requests for all trees + // for only 1 group + if (groupedTrees.Count == 1) { - var collection = new TreeNodeCollection(); - foreach (var apptree in allTrees) + var nodes = new TreeNodeCollection(); + foreach (var t 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 node = await TryGetRootNode(t, querystring); + if (node != null) + nodes.Add(node); } - if(collection.Count > 0) - { - var multiTree = TreeRootNode.CreateMultiTreeRoot(collection); - multiTree.Name = Services.TextService.Localize("sections/" + application); + var name = Services.TextService.Localize("sections/" + application); - return multiTree; + if (nodes.Count > 0) + { + var treeRootNode = TreeRootNode.CreateMultiTreeRoot(nodes); + treeRootNode.Name = name; + return treeRootNode; } - //Otherwise its a application/section with no trees (aka a full screen app) - //For example we do not have a Forms tree defined in C# & can not attribute with [Tree(isSingleNodeTree:true0] - var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - var section = Services.TextService.Localize("sections/" + application); - - return TreeRootNode.CreateSingleTreeRoot(rootId, null, null, section, TreeNodeCollection.Empty, true); + // otherwise it's a section with no tree, aka a fullscreen section + // todo is this true? what if we just failed to TryGetRootNode on all of them? + return TreeRootNode.CreateSingleTreeRoot(Constants.System.Root.ToInvariantString(), null, null, name, TreeNodeCollection.Empty, true); } - var rootNodeGroups = new List(); - - //Group trees by [CoreTree] attribute with a TreeGroup property - foreach (var treeSectionGroup in groupedTrees) + // for many groups + var treeRootNodes = new List(); + foreach (var (groupName, trees) in groupedTrees) { - var treeGroupName = treeSectionGroup.Key; - - var groupNodeCollection = new TreeNodeCollection(); - foreach (var appTree in treeSectionGroup.Value) + var nodes = new TreeNodeCollection(); + foreach (var t in trees) { - var rootNode = await GetRootForMultipleAppTree(appTree, queryStrings); - if (rootNode != null) - { - //Add to a new list/collection - groupNodeCollection.Add(rootNode); - } + var node = await TryGetRootNode(t, querystring); + if (node != null) + nodes.Add(node); } - //If treeGroupName == null then its third party - if (treeGroupName.IsNullOrWhiteSpace()) - { - //This is used for the localization key - //treeHeaders/thirdPartyGroup - treeGroupName = "thirdPartyGroup"; - } + if (nodes.Count == 0) + continue; - if (groupNodeCollection.Count > 0) - { - var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection, application); - groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); + // no name => third party + // use localization key treeHeaders/thirdPartyGroup + // todo this is an odd convention + var name = groupName.IsNullOrWhiteSpace() ? "thirdPartyGroup" : groupName; - rootNodeGroups.Add(groupRoot); - } + var groupRootNode = TreeRootNode.CreateGroupNode(nodes, application); + groupRootNode.Name = Services.TextService.Localize("treeHeaders/" + name); + treeRootNodes.Add(groupRootNode); } - return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(rootNodeGroups.OrderBy(x => x.Name))); + return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(treeRootNodes.OrderBy(x => x.Name))); } /// - /// Get the root node for an application with multiple trees + /// Tries to get the root node of a tree. /// - /// - /// - /// - private async Task GetRootForMultipleAppTree(Tree tree, FormDataCollection queryStrings) + /// + /// Returns null if the root node could not be obtained due to an HttpResponseException, + /// which probably indicates that the user isn't authorized to view that tree. + /// + private async Task TryGetRootNode(Tree tree, FormDataCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); + try { - var byControllerAttempt = await TryGetRootNodeFromControllerTree(tree, queryStrings, ControllerContext); - if (byControllerAttempt.Success) - { - return byControllerAttempt.Result; - } + return await GetRootNode(tree, querystring); } catch (HttpResponseException) { - //if this occurs its because the user isn't authorized to view that tree, in this case since we are loading multiple trees we - //will just return null so that it's not added to the list. + // if this occurs its because the user isn't authorized to view that tree, + // in this case since we are loading multiple trees we will just return + // null so that it's not added to the list. return null; } - - throw new ApplicationException("Could not get root node for tree type " + tree.TreeAlias); } /// - /// Get the root node for an application with one tree + /// Get the tree root node of a tree. /// - /// - /// - /// - /// - /// - private async Task GetRootForSingleAppTree(Tree tree, string id, FormDataCollection queryStrings, string application) + private async Task GetTreeRootNode(Tree tree, int id, FormDataCollection querystring) { - var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); if (tree == null) throw new ArgumentNullException(nameof(tree)); - var byControllerAttempt = TryLoadFromControllerTree(tree, id, queryStrings, ControllerContext); - if (!byControllerAttempt.Success) - throw new ApplicationException("Could not render a tree for type " + tree.TreeAlias); - var rootNode = await TryGetRootNodeFromControllerTree(tree, queryStrings, ControllerContext); - if (rootNode.Success == false) - { - //This should really never happen if we've successfully got the children above. - throw new InvalidOperationException("Could not create root node for tree " + tree.TreeAlias); - } + var children = await GetChildren(tree, id, querystring); + var rootNode = await GetRootNode(tree, querystring); var sectionRoot = TreeRootNode.CreateSingleTreeRoot( - rootId, - rootNode.Result.ChildNodesUrl, - rootNode.Result.MenuUrl, - rootNode.Result.Name, - byControllerAttempt.Result, + Constants.System.Root.ToInvariantString(), + rootNode.ChildNodesUrl, + rootNode.MenuUrl, + rootNode.Name, + children, tree.IsSingleNodeTree); - //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; - sectionRoot.Path = rootNode.Result.Path; + // 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.RoutePath; + sectionRoot.Path = rootNode.Path; - foreach (var d in rootNode.Result.AdditionalData) - { + foreach (var d in rootNode.AdditionalData) sectionRoot.AdditionalData[d.Key] = d.Value; - } - return sectionRoot; + return sectionRoot; } /// - /// Proxies a request to the destination tree controller to get it's root tree node + /// Gets the root node of a tree. /// - /// - /// - /// - /// - /// - /// This ensures that authorization filters are applied to the sub request - /// - private async Task> TryGetRootNodeFromControllerTree(Tree appTree, FormDataCollection formCollection, HttpControllerContext controllerContext) + private async Task GetRootNode(Tree tree, FormDataCollection querystring) { - //instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeController)DependencyResolver.Current.GetService(appTree.TreeControllerType); + if (tree == null) throw new ArgumentNullException(nameof(tree)); - //NOTE: This is all required in order to execute the auth-filters for the sub request, we + var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring); + var rootNode = controller.GetRootNode(querystring); + if (rootNode == null) + throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); + return rootNode; + } + + /// + /// Get the child nodes of a tree node. + /// + private async Task GetChildren(Tree tree, int id, FormDataCollection querystring) + { + if (tree == null) throw new ArgumentNullException(nameof(tree)); + + // the method we proxy has an 'id' parameter which is *not* in the querystring, + // we need to add it for the proxy to work (else, it does not find the method, + // when trying to run auth filters etc). + var d = querystring?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); + d["id"] = null; + var proxyQuerystring = new FormDataCollection(d); + + var controller = (TreeController) await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring); + return controller.GetNodes(id.ToInvariantString(), querystring); + } + + /// + /// Gets a proxy to a controller for a specified action. + /// + /// The type of the controller. + /// The action. + /// The querystring. + /// An instance of the controller. + /// + /// Creates an instance of the and initializes it with a route + /// and context etc. so it can execute the specified . Runs the authorization + /// filters for that action, to ensure that the user has permission to execute it. + /// + private async Task GetApiControllerProxy(Type controllerType, string action, FormDataCollection querystring) + { + // note: this is all required in order to execute the auth-filters for the sub request, we // need to "trick" web-api into thinking that it is actually executing the proxied controller. - var urlHelper = controllerContext.Request.GetUrlHelper(); - //create the proxied URL for the controller action - var proxiedUrl = controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Authority) + - urlHelper.GetUmbracoApiService("GetRootNode", instance.GetType()); - //add the query strings to it - proxiedUrl += "?" + formCollection.ToQueryString(); - //create proxy route data specifying the action / controller to execute - var proxiedRouteData = new HttpRouteData( - controllerContext.RouteData.Route, - new HttpRouteValueDictionary(new { action = "GetRootNode", controller = ControllerExtensions.GetControllerName(instance.GetType()) })); + var context = ControllerContext; - //create a proxied controller context - var proxiedControllerContext = new HttpControllerContext( - controllerContext.Configuration, - proxiedRouteData, - new HttpRequestMessage(HttpMethod.Get, proxiedUrl)) + // get the controller + var controller = (ApiController) DependencyResolver.Current.GetService(controllerType) + ?? throw new Exception($"Failed to create controller of type {controllerType.FullName}."); + + // create the proxy URL for the controller action + var proxyUrl = context.Request.RequestUri.GetLeftPart(UriPartial.Authority) + + context.Request.GetUrlHelper().GetUmbracoApiService(action, controllerType) + + "?" + querystring.ToQueryString(); + + // create proxy route data specifying the action & controller to execute + var proxyRoute = new HttpRouteData( + context.RouteData.Route, + new HttpRouteValueDictionary(new { action, controller = ControllerExtensions.GetControllerName(controllerType) })); + + // create a proxy request + var proxyRequest = new HttpRequestMessage(HttpMethod.Get, proxyUrl); + + // create a proxy controller context + var proxyContext = new HttpControllerContext(context.Configuration, proxyRoute, proxyRequest) { - ControllerDescriptor = new HttpControllerDescriptor(controllerContext.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(instance.GetType()), instance.GetType()), - RequestContext = controllerContext.RequestContext + ControllerDescriptor = new HttpControllerDescriptor(context.ControllerDescriptor.Configuration, ControllerExtensions.GetControllerName(controllerType), controllerType), + RequestContext = context.RequestContext, + Controller = controller }; - instance.ControllerContext = proxiedControllerContext; - instance.Request = controllerContext.Request; - instance.RequestContext.RouteData = proxiedRouteData; + // wire everything + controller.ControllerContext = proxyContext; + controller.Request = proxyContext.Request; + controller.RequestContext.RouteData = proxyRoute; - //invoke auth filters for this sub request - var result = await instance.ControllerContext.InvokeAuthorizationFiltersForRequest(); - //if a result is returned it means they are unauthorized, just throw the response. - if (result != null) - { - throw new HttpResponseException(result); - } + // auth + var authResult = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(); + if (authResult != null) + throw new HttpResponseException(authResult); - //return the root - var node = instance.GetRootNode(formCollection); - return node == null - ? Attempt.Fail(new InvalidOperationException("Could not return a root node for tree " + appTree.TreeAlias)) - : Attempt.Succeed(node); + return controller; } - - /// - /// Proxies a request to the destination tree controller to get it's tree node collection - /// - /// - /// - /// - /// - /// - private Attempt TryLoadFromControllerTree(Tree appTree, string id, FormDataCollection formCollection, HttpControllerContext controllerContext) - { - // instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeController)DependencyResolver.Current.GetService(appTree.TreeControllerType); - if (instance == null) - throw new Exception("Failed to create tree " + appTree.TreeControllerType + "."); - - // TODO: Shouldn't we be applying the same proxying logic as above so that filters work? seems like an oversight - - instance.ControllerContext = controllerContext; - instance.Request = controllerContext.Request; - - // return its data - return Attempt.Succeed(instance.GetNodes(id, formCollection)); - } - } } diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index ba851191b2..87a707102d 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -18,9 +18,9 @@ namespace Umbraco.Web.Trees /// This authorizes based on access to the content section even though it exists in the settings /// [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 12)] + [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, SortOrder = 12, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class ContentBlueprintTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 65a2aa3fb5..f179cb3ac6 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -15,9 +15,9 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] + [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, SortOrder = 0, TreeGroup = Constants.Trees.Groups.Settings)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs index 1b485aea6a..2d6ffe6a15 100644 --- a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs @@ -3,19 +3,12 @@ namespace Umbraco.Web.Trees { /// - /// Indicates that a tree is a core tree and shouldn't be treated as a plugin tree + /// Indicates that a tree is a core tree and should not be treated as a plugin tree. /// /// - /// This ensures that umbraco will look in the umbraco folders for views for this tree + /// This ensures that umbraco will look in the umbraco folders for views for this tree. /// [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 e60bbe49ed..8a0a83d94f 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -17,9 +17,9 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:3)] + [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, SortOrder = 3, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class DataTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -121,7 +121,7 @@ namespace Umbraco.Web.Trees }); if (container.HasChildren == false) - { + { //can delete data type menu.Items.Add(Services.TextService, opensDialog: true); } diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index 4ff3697a17..5f0b7b5df0 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -17,8 +17,8 @@ namespace Umbraco.Web.Trees // dictionary items in templates, even when we dont have authorization to manage the dictionary items )] [Mvc.PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] - [Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, null)] + [CoreTree] + [Tree(Constants.Applications.Translation, Constants.Trees.Dictionary, TreeGroup = Constants.Trees.Groups.Settings)] public class DictionaryTreeController : TreeController { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/FilesTreeController.cs b/src/Umbraco.Web/Trees/FilesTreeController.cs index 429b08e9ee..878bde1ca0 100644 --- a/src/Umbraco.Web/Trees/FilesTreeController.cs +++ b/src/Umbraco.Web/Trees/FilesTreeController.cs @@ -4,11 +4,11 @@ using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] - [Tree(Constants.Applications.Settings, "files", "Files", "icon-folder", "icon-folder", sortOrder: 13, initialize: false)] + [Tree(Constants.Applications.Settings, "files", TreeTitle = "Files", IconOpen = "icon-folder", IconClosed = "icon-folder", TreeUse = TreeUse.Dialog)] + [CoreTree] public class FilesTreeController : FileSystemTreeController { - protected override IFileSystem FileSystem => new PhysicalFileSystem("~/"); // TODO: inject + protected override IFileSystem FileSystem => new PhysicalFileSystem("~/"); private static readonly string[] ExtensionsStatic = { "*" }; diff --git a/src/Umbraco.Web/Trees/ITree.cs b/src/Umbraco.Web/Trees/ITree.cs index 1125a80fe2..7cd7e4221a 100644 --- a/src/Umbraco.Web/Trees/ITree.cs +++ b/src/Umbraco.Web/Trees/ITree.cs @@ -1,7 +1,7 @@ namespace Umbraco.Web.Trees { // TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used - //leave as internal for now, maybe we'll use in the future, means we could pass around ITree + //leave as internal for now, maybe we'll use in the future, means we could pass around ITree internal interface ITree { /// @@ -13,7 +13,12 @@ /// /// Gets the section alias. /// - string ApplicationAlias { get; } + string SectionAlias { get; } + + /// + /// Gets the tree group. + /// + string TreeGroup { get; } /// /// Gets the tree alias. @@ -25,6 +30,11 @@ /// string TreeTitle { get; } + /// + /// Gets the tree use. + /// + TreeUse TreeUse { get; } + /// /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) /// diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index 5b0ed8701f..ac2c0571e0 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -7,9 +7,9 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Languages)] - [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 11)] + [Tree(Constants.Applications.Settings, Constants.Trees.Languages, SortOrder = 11, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class LanguageTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/LogViewerTreeController.cs b/src/Umbraco.Web/Trees/LogViewerTreeController.cs index 22a1c799d6..3d9490b9a0 100644 --- a/src/Umbraco.Web/Trees/LogViewerTreeController.cs +++ b/src/Umbraco.Web/Trees/LogViewerTreeController.cs @@ -7,9 +7,8 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.LogViewer)] - [Tree(Constants.Applications.Settings, Constants.Trees.LogViewer, null, sortOrder: 9)] + [Tree(Constants.Applications.Settings, Constants.Trees.LogViewer, SortOrder= 9, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class LogViewerTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 0300dbd6c6..77b13416ff 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -12,9 +12,9 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Macros)] - [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] + [Tree(Constants.Applications.Settings, Constants.Trees.Macros, TreeTitle = "Macros", SortOrder = 4, TreeGroup = Constants.Trees.Groups.Settings)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class MacrosTreeController : TreeController { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 8420126db4..5901d713ec 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -16,9 +16,9 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:1)] + [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, SortOrder = 1, TreeGroup = Constants.Trees.Groups.Settings)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs index 9c8c8ea4e0..ea2412e4bd 100644 --- a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -8,7 +8,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.MemberGroups)] - [Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, null, sortOrder: 1)] + [Tree(Constants.Applications.Members, Constants.Trees.MemberGroups, SortOrder = 1)] [Mvc.PluginController("UmbracoTrees")] [CoreTree] public class MemberGroupTreeController : MemberTypeAndGroupTreeControllerBase diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index d1219da466..03c68dd67a 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members)] - [Tree(Constants.Applications.Members, Constants.Trees.Members, null, sortOrder: 0)] + [Tree(Constants.Applications.Members, Constants.Trees.Members, SortOrder = 0)] [PluginController("UmbracoTrees")] [CoreTree] [SearchableTree("searchResultFormatter", "configureMemberResult")] diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 7bf04010f2..3a72460963 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -7,9 +7,9 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)] + [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, SortOrder = 2, TreeGroup = Constants.Trees.Groups.Settings)] public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index 68b67f9fe2..9b1bf98823 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -8,7 +8,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Packages)] - [Tree(Constants.Applications.Packages, Constants.Trees.Packages, null, sortOrder: 0, isSingleNodeTree: true)] + [Tree(Constants.Applications.Packages, Constants.Trees.Packages, SortOrder = 0, IsSingleNodeTree = true)] [PluginController("UmbracoTrees")] [CoreTree] public class PackagesTreeController : TreeController diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index f8c3fe3e9a..e7d9972589 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -10,10 +10,10 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial view macros in the developer app /// - [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, SortOrder = 8, TreeGroup = Constants.Trees.Groups.Templating)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] + [CoreTree] 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 113f907e12..908e29b38a 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -10,10 +10,10 @@ namespace Umbraco.Web.Trees /// /// Tree for displaying partial views in the settings app /// - [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 7)] + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, SortOrder = 7, TreeGroup = Constants.Trees.Groups.Templating)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] + [CoreTree] public class PartialViewsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index 1888044d8d..82e07c5226 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -9,9 +9,9 @@ using Umbraco.Web.Actions; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] - [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)] + [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, SortOrder = 5, TreeGroup = Constants.Trees.Groups.Settings)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + [CoreTree] 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 bb7fd80f5b..1587e447ec 100644 --- a/src/Umbraco.Web/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -5,8 +5,8 @@ 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)] + [CoreTree] + [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)] public class ScriptsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.ScriptsFileSystem; // TODO: inject diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs index 0b82291919..87795fe8de 100644 --- a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -4,8 +4,8 @@ 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)] + [CoreTree] + [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", IconOpen = "icon-folder", IconClosed = "icon-folder", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)] public class StylesheetsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.StylesheetsFileSystem; // TODO: inject diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 77102dff45..79a59435a0 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -16,9 +16,9 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Templates)] - [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:6)] + [Tree(Constants.Applications.Settings, Constants.Trees.Templates, SortOrder = 6, TreeGroup = Constants.Trees.Groups.Templating)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] + [CoreTree] public class TemplatesTreeController : TreeController, ISearchableTree { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/Tree.cs b/src/Umbraco.Web/Trees/Tree.cs index 39f5cec1eb..37b379e292 100644 --- a/src/Umbraco.Web/Trees/Tree.cs +++ b/src/Umbraco.Web/Trees/Tree.cs @@ -1,46 +1,48 @@ using System; using System.Diagnostics; using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { - [DebuggerDisplay("Tree - {TreeAlias} ({ApplicationAlias})")] + [DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")] public class Tree : ITree { - public Tree(int sortOrder, string applicationAlias, string alias, string title, Type treeControllerType, bool isSingleNodeTree) + public Tree(int sortOrder, string applicationAlias, string group, string alias, string title, TreeUse use, Type treeControllerType, bool isSingleNodeTree) { SortOrder = sortOrder; - ApplicationAlias = applicationAlias; + SectionAlias = applicationAlias; + TreeGroup = group; TreeAlias = alias; TreeTitle = title; + TreeUse = use; TreeControllerType = treeControllerType; IsSingleNodeTree = isSingleNodeTree; } - + /// - /// - /// Gets or sets the sort order. - /// public int SortOrder { get; set; } - /// - /// Gets the application alias. - /// - public string ApplicationAlias { get; set; } + /// + public string SectionAlias { get; set; } + + /// + public string TreeGroup { get; } /// public string TreeAlias { get; } /// - /// - /// Gets or sets the tree title (fallback if the tree alias isn't localized) - /// - /// The title. public string TreeTitle { get; set; } + /// + public TreeUse TreeUse { get; set; } + + /// public bool IsSingleNodeTree { get; } + /// + /// Gets the tree controller type. + /// public Type TreeControllerType { get; } internal static string GetRootNodeDisplayName(ITree tree, ILocalizedTextService textService) @@ -64,6 +66,5 @@ namespace Umbraco.Web.Trees return label; } - } } diff --git a/src/Umbraco.Web/Trees/TreeAttribute.cs b/src/Umbraco.Web/Trees/TreeAttribute.cs index dd63f8c172..5b51d8605e 100644 --- a/src/Umbraco.Web/Trees/TreeAttribute.cs +++ b/src/Umbraco.Web/Trees/TreeAttribute.cs @@ -1,10 +1,9 @@ using System; -using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { /// - /// Identifies an application tree + /// Identifies a section tree. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class TreeAttribute : Attribute, ITree @@ -12,54 +11,55 @@ namespace Umbraco.Web.Trees /// /// Initializes a new instance of the class. /// - /// The app alias. - /// - public TreeAttribute(string appAlias, - string treeAlias) : this(appAlias, treeAlias, null) + public TreeAttribute(string sectionAlias, string treeAlias) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The app alias. - /// - /// - /// The icon closed. - /// The icon open. - /// if set to true [initialize]. - /// The sort order. - /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) - public TreeAttribute(string appAlias, - string treeAlias, - string treeTitle, - string iconClosed = "icon-folder", - string iconOpen = "icon-folder-open", - bool initialize = true, - int sortOrder = 0, - bool isSingleNodeTree = false) - { - ApplicationAlias = appAlias; + SectionAlias = sectionAlias; TreeAlias = treeAlias; - TreeTitle = treeTitle; - IconClosed = iconClosed; - IconOpen = iconOpen; - Initialize = initialize; - SortOrder = sortOrder; - IsSingleNodeTree = isSingleNodeTree; } - public string ApplicationAlias { get; } - public string TreeAlias { get; } - public string TreeTitle { get; } - public string IconClosed { get; } - public string IconOpen { get; } - public bool Initialize { get; } - public int SortOrder { get; } + /// + /// Gets the section alias. + /// + public string SectionAlias { get; } /// - /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) + /// Gets the tree alias. /// - public bool IsSingleNodeTree { get; } + public string TreeAlias { get; } + + /// + /// Gets or sets the tree title. + /// + public string TreeTitle { get; set; } + + /// + /// Gets or sets the group of the tree. + /// + public string TreeGroup { get; set; } + + /// + /// Gets the usage of the tree. + /// + public TreeUse TreeUse { get; set; } = TreeUse.Main; + + /// + /// Gets or sets the tree icon when closed. + /// + public string IconClosed { get; set; } = "icon-folder"; + + /// + /// Gets or sets the tree icon when open. + /// + public string IconOpen { get; set; } = "icon-folder-open"; + + /// + /// Gets or sets the tree sort order. + /// + public int SortOrder { get; set; } + + /// + /// Gets or sets a value indicating whether the tree is a single-node tree (no child nodes, full screen app). + /// + public bool IsSingleNodeTree { get; set; } } } diff --git a/src/Umbraco.Web/Trees/TreeCollection.cs b/src/Umbraco.Web/Trees/TreeCollection.cs index a7bfe52295..76404d4ac5 100644 --- a/src/Umbraco.Web/Trees/TreeCollection.cs +++ b/src/Umbraco.Web/Trees/TreeCollection.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Core.Models.ContentEditing; -using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Trees { + /// + /// Represents the collection of section trees. + /// public class TreeCollection : BuilderCollectionBase { public TreeCollection(IEnumerable items) diff --git a/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs b/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs index ae2675bac9..a62e474e43 100644 --- a/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs +++ b/src/Umbraco.Web/Trees/TreeCollectionBuilder.cs @@ -1,17 +1,43 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Composing; namespace Umbraco.Web.Trees { + /// + /// Builds a . + /// public class TreeCollectionBuilder : ICollectionBuilder { - /// - /// expose the list of trees which developers can manipulate before the collection is created - /// - public List Trees { get; } = new List(); + private readonly List _trees = new List(); - public TreeCollection CreateCollection(IFactory factory) => new TreeCollection(Trees); + public TreeCollection CreateCollection(IFactory factory) => new TreeCollection(_trees); public void RegisterWith(IRegister register) => register.Register(CreateCollection, Lifetime.Singleton); + + public void AddTreeController() + where TController : TreeControllerBase + => AddTreeController(typeof(TController)); + + public void AddTreeController(Type controllerType) + { + if (!typeof(TreeControllerBase).IsAssignableFrom(controllerType)) + throw new ArgumentException($"Type {controllerType} does not inherit from {typeof(TreeControllerBase).FullName}."); + + // no all TreeControllerBase are meant to be used here, + // ignore those that don't have the attribute + + var attribute = controllerType.GetCustomAttribute(false); + if (attribute == null) return; + var tree = new Tree(attribute.SortOrder, attribute.SectionAlias, attribute.TreeGroup, attribute.TreeAlias, attribute.TreeTitle, attribute.TreeUse, controllerType, attribute.IsSingleNodeTree); + _trees.Add(tree); + } + + public void AddTreeControllers(IEnumerable controllerTypes) + { + foreach (var controllerType in controllerTypes) + AddTreeController(controllerType); + } } } diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index 733c526331..e6d948e19a 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -15,53 +14,53 @@ namespace Umbraco.Web.Trees /// public abstract class TreeController : TreeControllerBase { - private TreeAttribute _attribute; + private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); - protected TreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) + private readonly TreeAttribute _treeAttribute; + + protected TreeController(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) + : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) { - Initialize(); + _treeAttribute = GetTreeAttribute(); } protected TreeController() { - Initialize(); + _treeAttribute = GetTreeAttribute(); } /// public override string RootNodeDisplayName => Tree.GetRootNodeDisplayName(this, Services.TextService); /// - public override string TreeAlias => _attribute.TreeAlias; - /// - public override string TreeTitle => _attribute.TreeTitle; - /// - public override string ApplicationAlias => _attribute.ApplicationAlias; - /// - public override int SortOrder => _attribute.SortOrder; - /// - public override bool IsSingleNodeTree => _attribute.IsSingleNodeTree; + public override string TreeGroup => _treeAttribute.TreeGroup; - private void Initialize() - { - _attribute = GetTreeAttribute(); - } + /// + public override string TreeAlias => _treeAttribute.TreeAlias; - private static readonly ConcurrentDictionary TreeAttributeCache = new ConcurrentDictionary(); + /// + public override string TreeTitle => _treeAttribute.TreeTitle; + + /// + public override TreeUse TreeUse => _treeAttribute.TreeUse; + + /// + public override string SectionAlias => _treeAttribute.SectionAlias; + + /// + public override int SortOrder => _treeAttribute.SortOrder; + + /// + public override bool IsSingleNodeTree => _treeAttribute.IsSingleNodeTree; private TreeAttribute GetTreeAttribute() { return TreeAttributeCache.GetOrAdd(GetType(), type => { - //Locate the tree attribute - var treeAttributes = type - .GetCustomAttributes(false) - .ToArray(); - - if (treeAttributes.Length == 0) + var treeAttribute = type.GetCustomAttribute(false); + if (treeAttribute == null) throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute"); - - //assign the properties of this object to those of the metadata attribute - return treeAttributes[0]; + return treeAttribute; }); } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 7b4c049353..8632c15d24 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -2,7 +2,6 @@ using System.Globalization; using System.Linq; using System.Net.Http.Formatting; -using System.Web.Http.Routing; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -19,7 +18,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { /// - /// A base controller reference for non-attributed trees (un-registered). + /// A base controller reference for non-attributed trees (un-registered). /// /// /// Developers should generally inherit from TreeController. @@ -28,13 +27,11 @@ namespace Umbraco.Web.Trees public abstract class TreeControllerBase : UmbracoAuthorizedApiController, ITree { protected TreeControllerBase() - { - } - - protected TreeControllerBase(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) - { - } + { } + protected TreeControllerBase(IGlobalSettings globalSettings, UmbracoContext umbracoContext, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState) + : base(globalSettings, umbracoContext, sqlContext, services, appCaches, logger, runtimeState) + { } /// /// The method called to render the contents of the tree structure @@ -62,14 +59,24 @@ namespace Umbraco.Web.Trees /// public abstract string RootNodeDisplayName { get; } + /// + public abstract string TreeGroup { get; } + /// public abstract string TreeAlias { get; } + /// public abstract string TreeTitle { get; } + /// - public abstract string ApplicationAlias { get; } + public abstract TreeUse TreeUse { get; } + + /// + public abstract string SectionAlias { get; } + /// public abstract int SortOrder { get; } + /// public abstract bool IsSingleNodeTree { get; } @@ -402,5 +409,4 @@ namespace Umbraco.Web.Trees handler?.Invoke(instance, e); } } - } diff --git a/src/Umbraco.Web/Trees/TreeUse.cs b/src/Umbraco.Web/Trees/TreeUse.cs new file mode 100644 index 0000000000..a1baee3332 --- /dev/null +++ b/src/Umbraco.Web/Trees/TreeUse.cs @@ -0,0 +1,26 @@ +using System; + +namespace Umbraco.Web.Trees +{ + /// + /// Defines tree uses. + /// + [Flags] + public enum TreeUse + { + /// + /// The tree is not used. + /// + None = 0, + + /// + /// The tree is used as a main (section) tree. + /// + Main = 1, + + /// + /// The tree is used as a dialog. + /// + Dialog = 2, + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index 95f041cac5..91078b2be8 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -7,7 +7,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Users)] - [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 0, isSingleNodeTree: true)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, SortOrder = 0, IsSingleNodeTree = true)] [PluginController("UmbracoTrees")] [CoreTree] public class UserTreeController : TreeController diff --git a/src/Umbraco.Web/TypeLoaderExtensions.cs b/src/Umbraco.Web/TypeLoaderExtensions.cs index 4d09783ca9..1cf8bc124c 100644 --- a/src/Umbraco.Web/TypeLoaderExtensions.cs +++ b/src/Umbraco.Web/TypeLoaderExtensions.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Media; using Umbraco.Web.Mvc; -using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Umbraco.Core.Composing; @@ -10,29 +8,20 @@ using Umbraco.Core.Composing; namespace Umbraco.Web { /// - /// Extension methods for the PluginTypemgr + /// Provides extension methods for the class. /// public static class TypeLoaderExtensions { /// - /// Returns all available TreeApiController's in application that are attribute with TreeAttribute + /// Gets all types implementing . /// - /// - /// - internal static IEnumerable GetAttributedTreeControllers(this TypeLoader mgr) - { - return mgr.GetTypesWithAttribute(); - } + internal static IEnumerable GetSurfaceControllers(this TypeLoader typeLoader) + => typeLoader.GetTypes(); - internal static IEnumerable GetSurfaceControllers(this TypeLoader mgr) - { - return mgr.GetTypes(); - } - - internal static IEnumerable GetUmbracoApiControllers(this TypeLoader mgr) - { - return mgr.GetTypes(); - } - + /// + /// Gets all types implementing . + /// + internal static IEnumerable GetUmbracoApiControllers(this TypeLoader typeLoader) + => typeLoader.GetTypes(); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 89e35b4e07..91ffbe68b5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -204,6 +204,7 @@ + @@ -1127,7 +1128,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs index 5ad3da7f4d..1bea963ee1 100644 --- a/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/UmbracoTreeAuthorizeAttribute.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.WebApi.Filters var apps = _treeAliases.Select(x => Current.TreeService .GetByAlias(x)) .WhereNotNull() - .Select(x => x.ApplicationAlias) + .Select(x => x.SectionAlias) .Distinct() .ToArray(); diff --git a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs index fee1f25ada..323e52eb36 100644 --- a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Runtime.Remoting.Contexts; using System.Threading; using System.Threading.Tasks; using System.Web.Http; @@ -15,52 +13,42 @@ namespace Umbraco.Web.WebApi internal static class HttpControllerContextExtensions { /// - /// This method will go an execute the authorization filters for the controller action, if any fail - /// it will return their response, otherwise we'll return null. + /// Invokes the authorization filters for the controller action. /// - /// + /// The response of the first filter returning a result, if any, otherwise null (authorized). internal static async Task InvokeAuthorizationFiltersForRequest(this HttpControllerContext controllerContext) { var controllerDescriptor = controllerContext.ControllerDescriptor; var controllerServices = controllerDescriptor.Configuration.Services; var actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext); - var actionContext = new HttpActionContext(controllerContext, actionDescriptor); + var filters = actionDescriptor.GetFilterPipeline(); var filterGrouping = new FilterGrouping(filters); - var actionFilters = filterGrouping.ActionFilters; - // Because the continuation gets built from the inside out we need to reverse the filter list - // so that least specific filters (Global) get run first and the most specific filters (Action) get run last. - var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToArray(); - if (authorizationFilters.Any()) - { - var cancelToken = new CancellationToken(); - var filterResult = await FilterContinuation(actionContext, cancelToken, authorizationFilters, 0); - if (filterResult != null) - { - //this means that the authorization filter has returned a result - unauthorized so we cannot continue - return filterResult; - } - } - return null; + // because the continuation gets built from the inside out we need to reverse the filter list + // so that least specific filters (Global) get run first and the most specific filters (Action) get run last. + var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList(); + + if (authorizationFilters.Count == 0) + return null; + + // if the authorization filter returns a result, it means it failed to authorize + var actionContext = new HttpActionContext(controllerContext, actionDescriptor); + return await ExecuteAuthorizationFiltersAsync(actionContext, CancellationToken.None, authorizationFilters); } /// - /// This method is how you execute a chain of filters, it needs to recursively call in to itself as the continuation for the next filter in the chain + /// Executes a chain of filters. /// - /// - /// - /// - /// - /// - private static async Task FilterContinuation(HttpActionContext actionContext, CancellationToken token, IList filters, int index) + /// + /// Recursively calls in to itself as its continuation for the next filter in the chain. + /// + private static async Task ExecuteAuthorizationFiltersAsync(HttpActionContext actionContext, CancellationToken token, IList filters, int index = 0) { return await filters[index].ExecuteAuthorizationFilterAsync(actionContext, token, - () => (index + 1) == filters.Count + () => ++index == filters.Count ? Task.FromResult(null) - : FilterContinuation(actionContext, token, filters, ++index)); + : ExecuteAuthorizationFiltersAsync(actionContext, token, filters, index)); } - - } } diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index d6826e5f90..2f99fe6350 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -4,6 +4,6 @@ Disposable construction HINT False - CSharp71 + Default True True