From 02764e0cccbe1c6f5daa4a4c348c6448fcdb534c Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 5 Mar 2019 13:02:59 +0100 Subject: [PATCH 01/31] Implement IPublishedContent Siblings --- src/Umbraco.Web/PublishedContentExtensions.cs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index f880076c50..dbd5008b74 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Examine; using Umbraco.Web.Composing; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web { @@ -21,6 +22,7 @@ namespace Umbraco.Web // see notes in PublishedElementExtensions // private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; + private static IPublishedSnapshot PublishedSnapshot => Current.PublishedSnapshot; #region Urls @@ -235,7 +237,8 @@ namespace Umbraco.Web /// /// The content items. /// The specific culture to filter for. If null is used the current culture is used. (Default is null). - internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, string culture = null) + internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, string culture = null) + where T : class, IPublishedContent { if (contents == null) throw new ArgumentNullException(nameof(contents)); @@ -1117,6 +1120,52 @@ namespace Umbraco.Web #endregion + #region Axes: siblings + + /// + /// Gets the siblings of the content. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content. + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + { + return content.Parent != null + ? content.Parent.Children(culture) + : PublishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(culture); + } + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The content type alias. + /// The siblings of the content, of the given content type. + public static IEnumerable SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + { + return content.Parent != null + ? content.Parent.ChildrenOfType(contentTypeAlias, culture) + : PublishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(culture); + } + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content type. + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content, of the given content type. + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + { + return content.Parent != null + ? content.Parent.Children(culture) + : PublishedSnapshot.Content.GetAtRoot().OfType().WhereIsInvariantOrHasCulture(culture); + } + + #endregion + #region Axes: custom /// From 7b815cfcb3ca7e454eb066ed6661d47d1916fd98 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 5 Mar 2019 21:34:49 +0100 Subject: [PATCH 02/31] Add methods for fetching multiple members to UmbracoHelper --- src/Umbraco.Web/UmbracoHelper.cs | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 12d4ce1d76..5d39f1c8dd 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -288,6 +288,56 @@ namespace Umbraco.Web var asInt = id.TryConvertTo(); return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } + + public IEnumerable Member(IEnumerable ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(IEnumerable ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(IEnumerable ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(IEnumerable ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(IEnumerable ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(params int[] ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(params string[] ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(params Guid[] ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(params Udi[] ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } + + public IEnumerable Member(params object[] ids) + { + return ids.Select(id => Member(id)).WhereNotNull(); + } #endregion From 36827cfa9f608659c2c7178b2d8fa8efb3437221 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 8 Mar 2019 07:31:39 +0100 Subject: [PATCH 03/31] Move some of the multiget member methods to MembershipHelper --- src/Umbraco.Web/Security/MembershipHelper.cs | 35 ++++++++++++++++++++ src/Umbraco.Web/UmbracoHelper.cs | 24 +++++++------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 3bb6951d9a..c2d80257e1 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -289,11 +289,46 @@ namespace Umbraco.Web.Security return MemberCache.GetByProviderKey(key); } + public virtual IEnumerable GetByProviderKey(IEnumerable keys) + { + return keys?.Select(GetByProviderKey).WhereNotNull() ?? new IPublishedContent[0]; + } + + public virtual IEnumerable GetByProviderKey(params object[] keys) + { + return keys?.Select(GetByProviderKey).WhereNotNull() ?? new IPublishedContent[0]; + } + public virtual IPublishedContent GetById(int memberId) { return MemberCache.GetById(memberId); } + public virtual IEnumerable GetById(IEnumerable memberIds) + { + return memberIds?.Select(GetById).WhereNotNull() ?? new IPublishedContent[0]; + } + + public virtual IEnumerable GetById(params int[] memberIds) + { + return memberIds?.Select(GetById).WhereNotNull() ?? new IPublishedContent[0]; + } + + public virtual IPublishedContent GetById(Guid memberId) + { + return GetByProviderKey(memberId); + } + + public virtual IEnumerable GetById(IEnumerable memberIds) + { + return GetByProviderKey(memberIds.OfType()); + } + + public virtual IEnumerable GetById(params Guid[] memberIds) + { + return GetByProviderKey(memberIds.OfType()); + } + public virtual IPublishedContent GetByUsername(string username) { return MemberCache.GetByUsername(username); diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 5d39f1c8dd..e9fdfd73e5 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -264,7 +264,7 @@ namespace Umbraco.Web public IPublishedContent Member(Guid id) { - return MembershipHelper.GetByProviderKey(id); + return MembershipHelper.GetById(id); } public IPublishedContent Member(object id) @@ -288,55 +288,55 @@ namespace Umbraco.Web var asInt = id.TryConvertTo(); return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } - + public IEnumerable Member(IEnumerable ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return MembershipHelper.GetById(ids); } public IEnumerable Member(IEnumerable ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(IEnumerable ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return MembershipHelper.GetById(ids); } public IEnumerable Member(IEnumerable ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(IEnumerable ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(params int[] ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(params string[] ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(params Guid[] ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return MembershipHelper.GetById(ids); } public IEnumerable Member(params Udi[] ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } public IEnumerable Member(params object[] ids) { - return ids.Select(id => Member(id)).WhereNotNull(); + return ids.Select(Member).WhereNotNull(); } #endregion From 1f7172e98dc4306c28dc2f2a5f4b9349d646d3ea Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Tue, 12 Mar 2019 21:54:13 +0000 Subject: [PATCH 04/31] Cleaned up class names in logviewer overview to use new logviewer classes and made a logviewer specific less stylesheet. Also left aligned the saved searches to make them look better --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-logviewer.less | 42 +++++++++++++++++++ .../src/views/logviewer/overview.html | 10 ++--- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 88988485fe..82fe416319 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -137,6 +137,7 @@ @import "components/umb-iconpicker.less"; @import "components/umb-insert-code-box.less"; @import "components/umb-packages.less"; +@import "components/umb-logviewer.less"; @import "components/umb-package-local-install.less"; @import "components/umb-panel-group.less"; @import "components/umb-lightbox.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less new file mode 100644 index 0000000000..f7aa0e4558 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -0,0 +1,42 @@ +/* PACKAGE DETAILS */ + +.umb-logviewer { + display: flex; + flex-flow: row wrap; +} + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-logviewer__main-content { + flex: 1 1 auto; + margin-right: 20px; + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling + min-width: 500px; + + .btn-link { + text-align: left; + } +} + +.umb-logviewer__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-logviewer { + flex-direction: column; + } + + .umb-logviewer__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-logviewer__sidebar { + flex: 1 1 auto; + width: 100%; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index 67d89e5ee7..a46853f97e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -1,4 +1,4 @@ -
+
@@ -24,8 +24,8 @@
-
-
+
+
@@ -68,7 +68,7 @@
-
+
@@ -95,4 +95,4 @@
-
\ No newline at end of file +
From 53e6c620eb13a9ee8e862953b1108d72ddb6295c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 13 Mar 2019 14:45:30 +0100 Subject: [PATCH 05/31] fixes 4627 + makes front-end validations appear as soon as they are invalid again. --- .../directives/validation/valpropertymsg.directive.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 9ee83dc2ba..c027e0778e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -62,8 +62,8 @@ function valPropertyMsg(serverValidationManager) { if (!watcher) { watcher = scope.$watch("currentProperty.value", function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { + + if (angular.equals(newValue, oldValue)) { return; } @@ -78,10 +78,12 @@ function valPropertyMsg(serverValidationManager) { // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg // is the only one, then we'll clear. - if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { + if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { scope.errorMsg = ""; formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); + } else if (showValidation && scope.errorMsg === "") { + formCtrl.$setValidity('valPropertyMsg', false); + scope.errorMsg = getErrorMsg(); } }, true); } @@ -152,6 +154,7 @@ function valPropertyMsg(serverValidationManager) { showValidation = true; if (hasError && scope.errorMsg === "") { scope.errorMsg = getErrorMsg(); + startWatch(); } else if (!hasError) { scope.errorMsg = ""; From 7f334540029f76761525b494c7d2ac40c9c6b98b Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 18 Mar 2019 17:51:04 +0100 Subject: [PATCH 06/31] Better ServerMessenger configuration --- src/Umbraco.Core/CompositionExtensions.cs | 22 +++++ .../BatchedDatabaseServerMessenger.cs | 5 +- ...aseServerRegistrarAndMessengerComponent.cs | 80 ++++++++----------- 3 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index e1c7ad4467..828a577c34 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -203,6 +203,28 @@ namespace Umbraco.Core composition.RegisterUnique(_ => registrar); } + /// + /// Sets the database server messenger options. + /// + /// The composition. + /// A function creating the options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerOptions(this Composition composition, Func factory) + { + composition.RegisterUnique(factory); + } + + /// + /// Sets the database server messenger options. + /// + /// The composition. + /// Options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerOptions(this Composition composition, DatabaseServerMessengerOptions options) + { + composition.RegisterUnique(_ => options); + } + /// /// Sets the short string helper. /// diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 76d7565862..818e8ecf77 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -27,9 +27,8 @@ namespace Umbraco.Web private readonly IUmbracoDatabaseFactory _databaseFactory; public BatchedDatabaseServerMessenger( - IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, - bool enableDistCalls, DatabaseServerMessengerOptions options) - : base(runtime, scopeProvider, sqlContext, proflog, globalSettings, enableDistCalls, options) + IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, DatabaseServerMessengerOptions options) + : base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options) { _databaseFactory = databaseFactory; } diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 086aa9b197..a68e137665 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -38,54 +38,44 @@ namespace Umbraco.Web.Compose public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer, ICoreComposer { + public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory) + { + var logger = factory.GetInstance(); + var indexRebuilder = factory.GetInstance(); + + return new DatabaseServerMessengerOptions + { + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => + { + // rebuild the published snapshot caches entirely, if the server is not synced + // this is equivalent to DistributedCache RefreshAll... but local only + // (we really should have a way to reuse RefreshAll... locally) + // note: refresh all content & media caches does refresh content types too + var svc = Current.PublishedSnapshotService; + svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); + svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _); + svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _); + }, + + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + () => { ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); } + } + }; + } + public override void Compose(Composition composition) { base.Compose(composition); - composition.SetServerMessenger(factory => - { - var runtime = factory.GetInstance(); - var databaseFactory = factory.GetInstance(); - var globalSettings = factory.GetInstance(); - var proflog = factory.GetInstance(); - var scopeProvider = factory.GetInstance(); - var sqlContext = factory.GetInstance(); - var logger = factory.GetInstance(); - var indexRebuilder = factory.GetInstance(); - - return new BatchedDatabaseServerMessenger( - runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings, - true, - //Default options for web including the required callbacks to build caches - new DatabaseServerMessengerOptions - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] - { - //rebuild the xml cache file if the server is not synced - () => - { - // rebuild the published snapshot caches entirely, if the server is not synced - // this is equivalent to DistributedCache RefreshAll... but local only - // (we really should have a way to reuse RefreshAll... locally) - // note: refresh all content & media caches does refresh content types too - var svc = Current.PublishedSnapshotService; - svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); - svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _); - svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _); - }, - - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - () => - { - ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); - } - } - }); - }); + composition.SetDatabaseServerMessengerOptions(GetDefaultOptions); + composition.SetServerMessenger(); } } @@ -128,7 +118,7 @@ namespace Umbraco.Web.Compose } public void Initialize() - { + { //We will start the whole process when a successful request is made if (_registrar != null || _messenger != null) UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; From e73029cf4fb486318dc0b6510cb2cc2115298137 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 Mar 2019 13:08:08 +0100 Subject: [PATCH 07/31] Rename siblings to ParentChildren (Because the actual returned list contains the content node self) --- src/Umbraco.Web/PublishedContentExtensions.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index dbd5008b74..8af2a933f2 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1120,15 +1120,15 @@ namespace Umbraco.Web #endregion - #region Axes: siblings + #region Axes: Parent Children (siblings including self) /// - /// Gets the siblings of the content. + /// Gets the children of the parent of the content. /// /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content. - public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + /// The children of the parent of the content. + public static IEnumerable ParentChildren(this IPublishedContent content, string culture = null) { return content.Parent != null ? content.Parent.Children(culture) @@ -1136,13 +1136,13 @@ namespace Umbraco.Web } /// - /// Gets the siblings of the content, of a given content type. + /// Gets the children of the parent of the content, of a given content type. /// /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// The content type alias. - /// The siblings of the content, of the given content type. - public static IEnumerable SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + /// The children of the parent of the content, of the given content type. + public static IEnumerable ParentChildrenOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.Parent != null ? content.Parent.ChildrenOfType(contentTypeAlias, culture) @@ -1150,13 +1150,13 @@ namespace Umbraco.Web } /// - /// Gets the siblings of the content, of a given content type. + /// Gets the children of the parent of the content, of a given content type. /// /// The content type. /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content, of the given content type. - public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + /// The children of the parent of the content, of the given content type. + public static IEnumerable ParentChildren(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.Parent != null From e4c6ef05a9e3e4d6f8b6d55026d3bd5f4654ee7a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 19 Mar 2019 14:59:52 +0100 Subject: [PATCH 08/31] Introduced both Siblings and SiblingsAndSelf --- .../PublishedContent/PublishedContentTests.cs | 89 +++++++++++++++++++ src/Umbraco.Web/PublishedContentExtensions.cs | 65 +++++++++++--- 2 files changed, 144 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index ab576171f4..de641a99a2 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -10,6 +10,7 @@ using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Core.Composing; using Moq; +using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -32,6 +33,8 @@ namespace Umbraco.Tests.PublishedContent protected override void Compose() { base.Compose(); + _publishedSnapshotAccessorMock = new Mock(); + Composition.RegisterUnique(_publishedSnapshotAccessorMock.Object); Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); Composition.RegisterUnique(); @@ -87,6 +90,7 @@ namespace Umbraco.Tests.PublishedContent } private readonly Guid _node1173Guid = Guid.NewGuid(); + private Mock _publishedSnapshotAccessorMock; protected override string GetXmlContent(int templateId) { @@ -792,6 +796,91 @@ namespace Umbraco.Tests.PublishedContent Assert.IsTrue(customDoc3.IsDescendantOrSelf(customDoc3)); } + [Test] + public void SiblingsAndSelf() + { + // Structure: + // - Root : 1046 (no parent) + // -- Level1.1: 1173 (parent 1046) + // --- Level1.1.1: 1174 (parent 1173) + // --- Level1.1.2: 117 (parent 1173) + // --- Level1.1.3: 1177 (parent 1173) + // --- Level1.1.4: 1178 (parent 1173) + // --- Level1.1.5: 1176 (parent 1173) + // -- Level1.2: 1175 (parent 1046) + // -- Level1.3: 4444 (parent 1046) + var root = GetNode(1046); + var level1_1 = GetNode(1173); + var level1_1_1 = GetNode(1174); + var level1_1_2 = GetNode(117); + var level1_1_3 = GetNode(1177); + var level1_1_4 = GetNode(1178); + var level1_1_5 = GetNode(1176); + var level1_2 = GetNode(1175); + var level1_3 = GetNode(4444); + + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + + CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf()); + + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_1.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_2.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_3.SiblingsAndSelf()); + + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_3.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_4.SiblingsAndSelf()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_5.SiblingsAndSelf()); + + } + + [Test] + public void Siblings() + { + // Structure: + // - Root : 1046 (no parent) + // -- Level1.1: 1173 (parent 1046) + // --- Level1.1.1: 1174 (parent 1173) + // --- Level1.1.2: 117 (parent 1173) + // --- Level1.1.3: 1177 (parent 1173) + // --- Level1.1.4: 1178 (parent 1173) + // --- Level1.1.5: 1176 (parent 1173) + // -- Level1.2: 1175 (parent 1046) + // -- Level1.3: 4444 (parent 1046) + var root = GetNode(1046); + var level1_1 = GetNode(1173); + var level1_1_1 = GetNode(1174); + var level1_1_2 = GetNode(117); + var level1_1_3 = GetNode(1177); + var level1_1_4 = GetNode(1178); + var level1_1_5 = GetNode(1176); + var level1_2 = GetNode(1175); + var level1_3 = GetNode(4444); + + _publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot()).Returns(new []{root}); + + CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings()); + + CollectionAssertAreEqual( new []{level1_2, level1_3}, level1_1.Siblings()); + CollectionAssertAreEqual( new []{level1_1, level1_3}, level1_2.Siblings()); + CollectionAssertAreEqual( new []{level1_1, level1_2}, level1_3.Siblings()); + + CollectionAssertAreEqual( new []{ level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_4, level1_1_5}, level1_1_3.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_5}, level1_1_4.Siblings()); + CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4}, level1_1_5.Siblings()); + + } + + private void CollectionAssertAreEqual(IEnumerable expected, IEnumerable actual) + where T: IPublishedContent + { + var e = expected.Select(x => x.Id); + var a = actual.Select(x => x.Id); + CollectionAssert.AreEquivalent(e, a, $"\nExpected:\n{string.Join(", ", e)}\n\nActual:\n{string.Join(", ", a)}"); + } [Test] public void FragmentProperty() diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 8af2a933f2..bba58dfae5 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1120,15 +1120,60 @@ namespace Umbraco.Web #endregion - #region Axes: Parent Children (siblings including self) + #region Axes: Siblings /// - /// Gets the children of the parent of the content. + /// Gets the siblings of the content. /// /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the parent of the content. - public static IEnumerable ParentChildren(this IPublishedContent content, string culture = null) + /// The siblings of the content. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + { + return SiblingsAndSelf(content, culture).Where(x => x.Id != content.Id); + } + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The content type alias. + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + { + return SiblingsAndSelfOfType(content, contentTypeAlias, culture).Where(x => x.Id != content.Id); + } + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content type. + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable Siblings(this IPublishedContent content, string culture = null) + where T : class, IPublishedContent + { + return SiblingsAndSelf(content, culture).Where(x => x.Id != content.Id); + } + + /// + /// Gets the siblings of the content including the node itself to indicate the position. + /// + /// The content. + /// The specific culture to filter for. If null is used the current culture is used. (Default is null) + /// The siblings of the content including the node itself. + public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) { return content.Parent != null ? content.Parent.Children(culture) @@ -1136,13 +1181,13 @@ namespace Umbraco.Web } /// - /// Gets the children of the parent of the content, of a given content type. + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. /// /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) /// The content type alias. - /// The children of the parent of the content, of the given content type. - public static IEnumerable ParentChildrenOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable SiblingsAndSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.Parent != null ? content.Parent.ChildrenOfType(contentTypeAlias, culture) @@ -1150,13 +1195,13 @@ namespace Umbraco.Web } /// - /// Gets the children of the parent of the content, of a given content type. + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. /// /// The content type. /// The content. /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the parent of the content, of the given content type. - public static IEnumerable ParentChildren(this IPublishedContent content, string culture = null) + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable SiblingsAndSelf(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.Parent != null From 080f4331402dca888c4867d774d79abfd1640e6d Mon Sep 17 00:00:00 2001 From: Brandon Osborne Date: Sat, 23 Mar 2019 03:59:04 -0500 Subject: [PATCH 09/31] Updated Upload method (#4850) --- src/Umbraco.Web/Editors/ContentTypeController.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index bf2ef62028..1f00b88b6d 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -552,9 +552,15 @@ namespace Umbraco.Web.Editors var file = result.FileData[0]; var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + // renaming the file because MultipartFormDataStreamProvider has created a random fileName instead of using the name from the + // content-disposition for more than 6 years now. Creating a CustomMultipartDataStreamProvider deriving from MultipartFormDataStreamProvider + // seems like a cleaner option, but I'm not sure where to put it and renaming only takes one line of code. + System.IO.File.Move(result.FileData[0].LocalFileName, root + "\\" + fileName); + if (ext.InvariantEquals("udt")) { - model.TempFileName = Path.Combine(root, model.TempFileName); + model.TempFileName = Path.Combine(root, fileName); model.UploadedFiles.Add(new ContentPropertyFile { From 4e1634754ba46dd571fb8dae9cf8dd096c634fed Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Sat, 23 Mar 2019 19:29:46 +1000 Subject: [PATCH 10/31] fix URL redirection creation - was failing when save and save+publish as save creates new URL, which is compared to itself in save+publish (#5036) --- .../Routing/RedirectTrackingEventHandler.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs index 75cce71f73..d032be2795 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingEventHandler.cs @@ -177,11 +177,13 @@ namespace Umbraco.Web.Routing // change the IUrlSegmentProvider to support being able to determine if a // segment is going to change for an entity. See notes in IUrlSegmentProvider. - var oldEntity = ApplicationContext.Current.Services.ContentService.GetById(entity.Id); - if (oldEntity == null) continue; - var oldSegment = oldEntity.GetUrlSegment(); + // we want the last published version, not the last saved + // otherwise redirects aren't created if content is saved, with save+publish happening later + var publishedEntity = ApplicationContext.Current.Services.ContentService.GetPublishedVersion(entity.Id); + if (publishedEntity == null) continue; + var publishedSegment = publishedEntity.GetUrlSegment(); var newSegment = entity.GetUrlSegment(); - process = oldSegment != newSegment; + process = publishedSegment != newSegment; } // skip if no segment change From bf3ce014cb50a62d6c964ac5cda32af68d35a668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 10:51:06 +0100 Subject: [PATCH 11/31] changed button styles for a better low hanging-fruit solution --- .../src/views/dashboard/content/redirecturls.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html index 98a8294e80..0f08897501 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/content/redirecturls.html @@ -13,7 +13,8 @@ type="button" size="s" action="vm.disableUrlTracker($event)" - label-key="redirectUrls_disableUrlTracker"> + label-key="redirectUrls_disableUrlTracker" + button-style="white"> + label-key="redirectUrls_enableUrlTracker" + button-style="success"> From 026ad4f2a8b0e6c3df50cbe2b89183409d6f08c2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Mar 2019 11:07:50 +0100 Subject: [PATCH 12/31] fixes - TinyMCE does not sync data to angular store when dragged away in grid-editor (#5048) --- .../src/views/components/grid/grid-rte.html | 4 +- .../propertyeditors/grid/grid.controller.js | 1831 +++++++++-------- .../src/views/propertyeditors/grid/grid.html | 31 +- 3 files changed, 933 insertions(+), 933 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html index f3b854a062..889c4133d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/grid/grid-rte.html @@ -1,3 +1,3 @@ -
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index e5a9711458..c23c2cd212 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -1,953 +1,954 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridController", - function ( - $scope, - localizationService, - gridService, - $timeout, - umbRequestHelper, - angularHelper, - $element, - eventsService - ) { + function ( + $scope, + localizationService, + gridService, + umbRequestHelper, + angularHelper, + $element, + eventsService + ) { - // Grid status variables - var placeHolder = ""; - var currentForm = angularHelper.getCurrentForm($scope); + // Grid status variables + var placeHolder = ""; + var currentForm = angularHelper.getCurrentForm($scope); - $scope.currentRow = null; - $scope.currentCell = null; - $scope.currentToolsControl = null; - $scope.currentControl = null; - $scope.openRTEToolbarId = null; - $scope.hasSettings = false; - $scope.showRowConfigurations = true; - $scope.sortMode = false; - $scope.reorderKey = "general_reorder"; + $scope.currentRow = null; + $scope.currentCell = null; + $scope.currentToolsControl = null; + $scope.currentControl = null; + $scope.openRTEToolbarId = null; + $scope.hasSettings = false; + $scope.showRowConfigurations = true; + $scope.sortMode = false; + $scope.reorderKey = "general_reorder"; - // ********************************************* - // Sortable options - // ********************************************* + // ********************************************* + // Sortable options + // ********************************************* - var draggedRteSettings; + var draggedRteSettings; - $scope.sortableOptionsRow = { - distance: 10, - cursor: "move", - placeholder: "ui-sortable-placeholder", - handle: ".umb-row-title-bar", - helper: "clone", - forcePlaceholderSize: true, - tolerance: "pointer", - zIndex: 999999999999999999, - scrollSensitivity: 100, - cursorAt: { - top: 40, - left: 60 - }, + $scope.sortableOptionsRow = { + distance: 10, + cursor: "move", + placeholder: "ui-sortable-placeholder", + handle: ".umb-row-title-bar", + helper: "clone", + forcePlaceholderSize: true, + tolerance: "pointer", + zIndex: 999999999999999999, + scrollSensitivity: 100, + cursorAt: { + top: 40, + left: 60 + }, - sort: function (event, ui) { - /* prevent vertical scroll out of the screen */ - var max = $(".umb-grid").width() - 150; - if (parseInt(ui.helper.css("left")) > max) { - ui.helper.css({ "left": max + "px" }); + sort: function (event, ui) { + /* prevent vertical scroll out of the screen */ + var max = $(".umb-grid").width() - 150; + if (parseInt(ui.helper.css("left")) > max) { + ui.helper.css({ "left": max + "px" }); + } + if (parseInt(ui.helper.css("left")) < 20) { + ui.helper.css({ "left": 20 }); + } + }, + + start: function (e, ui) { + + // Fade out row when sorting + ui.item[0].style.display = "block"; + ui.item[0].style.opacity = "0.5"; + + draggedRteSettings = {}; + ui.item.find(".umb-rte").each(function (key, value) { + // remove all RTEs in the dragged row and save their settings + var rteId = value.id; + draggedRteSettings[rteId] = _.findWhere(tinyMCE.editors, { id: rteId }).settings; + }); + }, + + stop: function (e, ui) { + + // Fade in row when sorting stops + ui.item[0].style.opacity = "1"; + + // reset all RTEs affected by the dragging + ui.item.parents(".umb-column").find(".umb-rte").each(function (key, value) { + var rteId = value.id; + draggedRteSettings[rteId] = draggedRteSettings[rteId] || _.findWhere(tinyMCE.editors, { id: rteId }).settings; + tinyMCE.execCommand("mceRemoveEditor", false, rteId); + tinyMCE.init(draggedRteSettings[rteId]); + }); + currentForm.$setDirty(); } - if (parseInt(ui.helper.css("left")) < 20) { - ui.helper.css({ "left": 20 }); - } - }, + }; - start: function (e, ui) { + var notIncludedRte = []; + var cancelMove = false; + var startingArea; + $scope.sortableOptionsCell = { + distance: 10, + cursor: "move", + uiFloating: true, + placeholder: "ui-sortable-placeholder", + handle: ".umb-control-handle", + helper: "clone", + connectWith: ".umb-cell-inner", + forcePlaceholderSize: true, + tolerance: "pointer", + zIndex: 999999999999999999, + scrollSensitivity: 100, + cursorAt: { + top: 45, + left: 90 + }, - // Fade out row when sorting - ui.item[0].style.display = "block"; - ui.item[0].style.opacity = "0.5"; + sort: function (event, ui) { - draggedRteSettings = {}; - ui.item.find(".mceNoEditor").each(function () { - // remove all RTEs in the dragged row and save their settings - var id = $(this).attr("id"); - draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings; - // tinyMCE.execCommand("mceRemoveEditor", false, id); - }); - }, + /* prevent vertical scroll out of the screen */ + var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css("left")) - parseInt($(".umb-grid").offset().left); + var max = $(".umb-grid").width() - 220; + if (position > max) { + ui.helper.css({ "left": max - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); + } + if (position < 0) { + ui.helper.css({ "left": 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); + } + }, - stop: function (e, ui) { + over: function (event, ui) { - // Fade in row when sorting stops - ui.item[0].style.opacity = "1"; + var area = event.target.getScope_HackForSortable().area; + var allowedEditors = area.allowed; - // reset all RTEs affected by the dragging - ui.item.parents(".umb-column").find(".mceNoEditor").each(function () { - var id = $(this).attr("id"); - draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand("mceRemoveEditor", false, id); - tinyMCE.init(draggedRteSettings[id]); - }); - currentForm.$setDirty(); - } - }; - - var notIncludedRte = []; - var cancelMove = false; - var startingArea; - - $scope.sortableOptionsCell = { - distance: 10, - cursor: "move", - placeholder: "ui-sortable-placeholder", - handle: ".umb-control-handle", - helper: "clone", - connectWith: ".umb-cell-inner", - forcePlaceholderSize: true, - tolerance: "pointer", - zIndex: 999999999999999999, - scrollSensitivity: 100, - cursorAt: { - top: 45, - left: 90 - }, - - sort: function (event, ui) { - - /* prevent vertical scroll out of the screen */ - var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css("left")) - parseInt($(".umb-grid").offset().left); - var max = $(".umb-grid").width() - 220; - if (position > max) { - ui.helper.css({ "left": max - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); - } - if (position < 0) { - ui.helper.css({ "left": 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); - } - }, - - over: function (event, ui) { - - var area = event.target.getScope_HackForSortable().area; - var allowedEditors = area.allowed; - - if (($.inArray(ui.item[0].getScope_HackForSortable().control.editor.alias, allowedEditors) < 0 && allowedEditors) || + if (($.inArray(ui.item[0].getScope_HackForSortable().control.editor.alias, allowedEditors) < 0 && allowedEditors) || (startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1)) { - $scope.$apply(function () { - event.target.getScope_HackForSortable().area.dropNotAllowed = true; - }); - - ui.placeholder.hide(); - cancelMove = true; - } - else { - if (event.target.getScope_HackForSortable().area.controls.length == 0){ - $scope.$apply(function () { - event.target.getScope_HackForSortable().area.dropOnEmpty = true; + event.target.getScope_HackForSortable().area.dropNotAllowed = true; }); + ui.placeholder.hide(); - } else { - ui.placeholder.show(); - } - cancelMove = false; - } - }, - - out: function(event, ui) { - $scope.$apply(function () { - var dropArea = event.target.getScope_HackForSortable().area; - dropArea.dropNotAllowed = false; - dropArea.dropOnEmpty = false; - }); - }, - - update: function (event, ui) { - /* add all RTEs which are affected by the dragging */ - if (!ui.sender) { - if (cancelMove) { - ui.item.sortable.cancel(); - } - ui.item.parents(".umb-cell.content").find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr("id")); - } - }); - } - else { - $(event.target).find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr("id")); - } - }); - } - currentForm.$setDirty(); - }, - - start: function (event, ui) { - //Get the starting area for reference - var area = event.target.getScope_HackForSortable().area; - startingArea = area; - - // fade out control when sorting - ui.item[0].style.display = "block"; - ui.item[0].style.opacity = "0.5"; - - // reset dragged RTE settings in case a RTE isn't dragged - draggedRteSettings = undefined; - ui.item[0].style.display = "block"; - ui.item.find(".mceNoEditor").each(function () { - notIncludedRte = []; - var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr("id") }); - - // save the dragged RTE settings - if(editors) { - draggedRteSettings = editors.settings; - - // remove the dragged RTE - tinyMCE.execCommand("mceRemoveEditor", false, $(this).attr("id")); - - } - - }); - }, - - stop: function (event, ui) { - - // Fade in control when sorting stops - ui.item[0].style.opacity = "1"; - - ui.item.offsetParent().find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { - // add all dragged's neighbouring RTEs in the new cell - notIncludedRte.splice(0, 0, $(this).attr("id")); - } - }); - $timeout(function () { - // reconstruct the dragged RTE (could be undefined when dragging something else than RTE) - if (draggedRteSettings !== undefined) { - tinyMCE.init(draggedRteSettings); - } - - _.forEach(notIncludedRte, function (id) { - // reset all the other RTEs - if (draggedRteSettings === undefined || id !== draggedRteSettings.id) { - var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand("mceRemoveEditor", false, id); - tinyMCE.init(rteSettings); - } - }); - }, 500, false); - - $scope.$apply(function () { - - var cell = event.target.getScope_HackForSortable().area; - cell.hasActiveChild = hasActiveChild(cell, cell.controls); - cell.active = false; - }); - } - - }; - - $scope.toggleSortMode = function() { - $scope.sortMode = !$scope.sortMode; - if($scope.sortMode) { - $scope.reorderKey = "general_reorderDone"; - } else { - $scope.reorderKey = "general_reorder"; - } - }; - - $scope.showReorderButton = function() { - if($scope.model.value && $scope.model.value.sections) { - for(var i = 0; $scope.model.value.sections.length > i; i++) { - var section = $scope.model.value.sections[i]; - if(section.rows && section.rows.length > 0) { - return true; - } - } - } - }; - - // ********************************************* - // Add items overlay menu - // ********************************************* - $scope.openEditorOverlay = function(event, area, index, key) { - var title = ""; - localizationService.localize("grid_insertControl").then(function(value){ - title = value; - $scope.editorOverlay = { - view: "itempicker", - filter: area.$allowedEditors.length > 15, - title: title, - availableItems: area.$allowedEditors, - event: event, - show: true, - submit: function(model) { - if (model.selectedItem) { - $scope.addControl(model.selectedItem, area, index); - $scope.editorOverlay.show = false; - $scope.editorOverlay = null; - } - } - }; - }); - }; - - // ********************************************* - // Template management functions - // ********************************************* - - $scope.addTemplate = function (template) { - $scope.model.value = angular.copy(template); - - //default row data - _.forEach($scope.model.value.sections, function (section) { - $scope.initSection(section); - }); - }; - - - // ********************************************* - // Row management function - // ********************************************* - - $scope.clickRow = function(index, rows) { - rows[index].active = true; - }; - - $scope.clickOutsideRow = function(index, rows) { - rows[index].active = false; - }; - - function getAllowedLayouts(section) { - - var layouts = $scope.model.config.items.layouts; - - //This will occur if it is a new section which has been - // created from a 'template' - if (section.allowed && section.allowed.length > 0) { - return _.filter(layouts, function (layout) { - return _.indexOf(section.allowed, layout.name) >= 0; - }); - } - else { - - - return layouts; - } - } - - $scope.addRow = function (section, layout, isInit) { - - //copy the selected layout into the rows collection - var row = angular.copy(layout); - - // Init row value - row = $scope.initRow(row); - - // Push the new row - if (row) { - section.rows.push(row); - } - if (!isInit) { - currentForm.$setDirty(); - } - - $scope.showRowConfigurations = false; - - eventsService.emit("grid.rowAdded", { scope: $scope, element: $element, row: row }); - - }; - - $scope.removeRow = function (section, $index) { - if (section.rows.length > 0) { - section.rows.splice($index, 1); - $scope.currentRow = null; - $scope.openRTEToolbarId = null; - currentForm.$setDirty(); - } - - if(section.rows.length === 0) { - $scope.showRowConfigurations = true; - } - }; - - var shouldApply = function(item, itemType, gridItem) { - if (item.applyTo === undefined || item.applyTo === null || item.applyTo === "") { - return true; - } - - if (typeof (item.applyTo) === "string") { - return item.applyTo === itemType; - } - - if (itemType === "row") { - if (item.applyTo.row === undefined) { - return false; - } - if (item.applyTo.row === null || item.applyTo.row === "") { - return true; - } - var rows = item.applyTo.row.split(','); - return _.indexOf(rows, gridItem.name) !== -1; - } else if (itemType === "cell") { - if (item.applyTo.cell === undefined) { - return false; - } - if (item.applyTo.cell === null || item.applyTo.cell === "") { - return true; - } - var cells = item.applyTo.cell.split(','); - var cellSize = gridItem.grid.toString(); - return _.indexOf(cells, cellSize) !== -1; - } - } - - $scope.editGridItemSettings = function (gridItem, itemType) { - - placeHolder = "{0}"; - - var styles, config; - if (itemType === 'control') { - styles = null; - config = angular.copy(gridItem.editor.config.settings); - } else { - styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); }); - config = _.filter(angular.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); }); - } - - if(angular.isObject(gridItem.config)){ - _.each(config, function(cfg){ - var val = gridItem.config[cfg.key]; - if(val){ - cfg.value = stripModifier(val, cfg.modifier); - } - }); - } - - if(angular.isObject(gridItem.styles)){ - _.each(styles, function(style){ - var val = gridItem.styles[style.key]; - if(val){ - style.value = stripModifier(val, style.modifier); - } - }); - } - - $scope.gridItemSettingsDialog = {}; - $scope.gridItemSettingsDialog.view = "views/propertyeditors/grid/dialogs/config.html"; - $scope.gridItemSettingsDialog.title = "Settings"; - $scope.gridItemSettingsDialog.styles = styles; - $scope.gridItemSettingsDialog.config = config; - - $scope.gridItemSettingsDialog.show = true; - - $scope.gridItemSettingsDialog.submit = function(model) { - - var styleObject = {}; - var configObject = {}; - - _.each(model.styles, function(style){ - if(style.value){ - styleObject[style.key] = addModifier(style.value, style.modifier); - } - }); - _.each(model.config, function (cfg) { - if (cfg.value) { - configObject[cfg.key] = addModifier(cfg.value, cfg.modifier); - } - }); - - gridItem.styles = styleObject; - gridItem.config = configObject; - gridItem.hasConfig = gridItemHasConfig(styleObject, configObject); - - currentForm.$setDirty(); - - $scope.gridItemSettingsDialog.show = false; - $scope.gridItemSettingsDialog = null; - }; - - $scope.gridItemSettingsDialog.close = function(oldModel) { - $scope.gridItemSettingsDialog.show = false; - $scope.gridItemSettingsDialog = null; - }; - - }; - - function stripModifier(val, modifier) { - if (!val || !modifier || modifier.indexOf(placeHolder) < 0) { - return val; - } else { - var paddArray = modifier.split(placeHolder); - if(paddArray.length == 1){ - if (modifier.indexOf(placeHolder) === 0) { - return val.slice(0, -paddArray[0].length); - } else { - return val.slice(paddArray[0].length, 0); - } - } else { - if (paddArray[1].length === 0) { - return val.slice(paddArray[0].length); - } - return val.slice(paddArray[0].length, -paddArray[1].length); - } - } - } - - var addModifier = function(val, modifier){ - if (!modifier || modifier.indexOf(placeHolder) < 0) { - return val; - } else { - return modifier.replace(placeHolder, val); - } - }; - - function gridItemHasConfig(styles, config) { - - if(_.isEmpty(styles) && _.isEmpty(config)) { - return false; - } else { - return true; - } - - } - - // ********************************************* - // Area management functions - // ********************************************* - - $scope.clickCell = function(index, cells, row) { - cells[index].active = true; - row.hasActiveChild = true; - }; - - $scope.clickOutsideCell = function(index, cells, row) { - cells[index].active = false; - row.hasActiveChild = hasActiveChild(row, cells); - }; - - $scope.cellPreview = function (cell) { - if (cell && cell.$allowedEditors) { - var editor = cell.$allowedEditors[0]; - return editor.icon; - } else { - return "icon-layout"; - } - }; - - - // ********************************************* - // Control management functions - // ********************************************* - $scope.clickControl = function (index, controls, cell) { - controls[index].active = true; - cell.hasActiveChild = true; - }; - - $scope.clickOutsideControl = function (index, controls, cell) { - controls[index].active = false; - cell.hasActiveChild = hasActiveChild(cell, controls); - }; - - function hasActiveChild(item, children) { - - var activeChild = false; - - for(var i = 0; children.length > i; i++) { - var child = children[i]; - - if(child.active) { - activeChild = true; - } - } - - if(activeChild) { - return true; - } - - } - - - var guid = (function () { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return function () { - return s4() + s4() + "-" + s4() + "-" + s4() + "-" + - s4() + "-" + s4() + s4() + s4(); - }; - })(); - - $scope.setUniqueId = function (cell, index) { - return guid(); - }; - - $scope.addControl = function (editor, cell, index, initialize) { - - initialize = (initialize !== false); - - var newControl = { - value: null, - editor: editor, - $initializing: initialize - }; - - if (index === undefined) { - index = cell.controls.length; - } - - newControl.active = true; - - //populate control - $scope.initControl(newControl, index + 1); - - cell.controls.push(newControl); - - eventsService.emit("grid.itemAdded", { scope: $scope, element: $element, cell: cell, item: newControl }); - - }; - - $scope.addTinyMce = function (cell) { - var rte = $scope.getEditor("rte"); - $scope.addControl(rte, cell); - }; - - $scope.getEditor = function (alias) { - return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; }); - }; - - $scope.removeControl = function (cell, $index) { - $scope.currentControl = null; - cell.controls.splice($index, 1); - }; - - $scope.percentage = function (spans) { - return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); - }; - - - $scope.clearPrompt = function (scopedObject, e) { - scopedObject.deletePrompt = false; - e.preventDefault(); - e.stopPropagation(); - }; - - $scope.togglePrompt = function (scopedObject) { - scopedObject.deletePrompt = !scopedObject.deletePrompt; - }; - - $scope.hidePrompt = function (scopedObject) { - scopedObject.deletePrompt = false; - }; - - $scope.toggleAddRow = function() { - $scope.showRowConfigurations = !$scope.showRowConfigurations; - }; - - - // ********************************************* - // Initialization - // these methods are called from ng-init on the template - // so we can controll their first load data - // - // intialization sets non-saved data like percentage sizing, allowed editors and - // other data that should all be pre-fixed with $ to strip it out on save - // ********************************************* - - // ********************************************* - // Init template + sections - // ********************************************* - $scope.initContent = function () { - var clear = true; - - //settings indicator shortcut - if (($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) { - $scope.hasSettings = true; - } - - //ensure the grid has a column value set, - //if nothing is found, set it to 12 - if (!$scope.model.config.items.columns){ - $scope.model.config.items.columns = 12; - } else if (angular.isString($scope.model.config.items.columns)) { - $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); - } - - if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) { - - if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) { - - //This will occur if it is an existing value, in which case - // we need to determine which layout was applied by looking up - // the name - // TODO: We need to change this to an immutable ID!! - - var found = _.find($scope.model.config.items.templates, function (t) { - return t.name === $scope.model.value.name; - }); - - if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) { - - //Cool, we've found the template associated with our current value with matching sections counts, now we need to - // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't - // allowed for this template based on the current config. - - _.each(found.sections, function (templateSection, index) { - angular.extend($scope.model.value.sections[index], angular.copy(templateSection)); - }); - - } - } - - _.forEach($scope.model.value.sections, function (section, index) { - - if (section.grid > 0) { - $scope.initSection(section); - - //we do this to ensure that the grid can be reset by deleting the last row - if (section.rows.length > 0) { - clear = false; - } - } else { - $scope.model.value.sections.splice(index, 1); - } - }); - } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) { - $scope.addTemplate($scope.model.config.items.templates[0]); - clear = false; - } - - if (clear) { - $scope.model.value = undefined; - } - }; - - $scope.initSection = function (section) { - section.$percentage = $scope.percentage(section.grid); - - section.$allowedLayouts = getAllowedLayouts(section); - - if (!section.rows || section.rows.length === 0) { - section.rows = []; - if(section.$allowedLayouts.length === 1){ - $scope.addRow(section, section.$allowedLayouts[0], true); - } - } else { - _.forEach(section.rows, function (row, index) { - if (!row.$initialized) { - var initd = $scope.initRow(row); - - //if init fails, remove - if (!initd) { - section.rows.splice(index, 1); - } else { - section.rows[index] = initd; - } - } - }); - - // if there is more than one row added - hide row add tools - $scope.showRowConfigurations = false; - } - }; - - - // ********************************************* - // Init layout / row - // ********************************************* - $scope.initRow = function (row) { - - //merge the layout data with the original config data - //if there are no config info on this, splice it out - var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; }); - - if (!original) { - return null; - } else { - //make a copy to not touch the original config - original = angular.copy(original); - original.styles = row.styles; - original.config = row.config; - original.hasConfig = gridItemHasConfig(row.styles, row.config); - - - //sync area configuration - _.each(original.areas, function (area, areaIndex) { - - - if (area.grid > 0) { - var currentArea = row.areas[areaIndex]; - - if (currentArea) { - area.config = currentArea.config; - area.styles = currentArea.styles; - area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config); - } - - //set editor permissions - if (!area.allowed || area.allowAll === true) { - area.$allowedEditors = $scope.availableEditors; - area.$allowsRTE = true; - } else { - area.$allowedEditors = _.filter($scope.availableEditors, function (editor) { - return _.indexOf(area.allowed, editor.alias) >= 0; - }); - - if (_.indexOf(area.allowed, "rte") >= 0) { - area.$allowsRTE = true; - } - } - - //copy over existing controls into the new areas - if (row.areas.length > areaIndex && row.areas[areaIndex].controls) { - area.controls = currentArea.controls; - - _.forEach(area.controls, function (control, controlIndex) { - $scope.initControl(control, controlIndex); - }); - - } else { - //if empty - area.controls = []; - - //if only one allowed editor - if(area.$allowedEditors.length === 1){ - $scope.addControl(area.$allowedEditors[0], area, 0, false); - } - } - - //set width - area.$percentage = $scope.percentage(area.grid); - area.$uniqueId = $scope.setUniqueId(); - - } else { - original.areas.splice(areaIndex, 1); - } - }); - - //replace the old row - original.$initialized = true; - - //set a disposable unique ID - original.$uniqueId = $scope.setUniqueId(); - - //set a no disposable unique ID (util for row styling) - original.id = !row.id ? $scope.setUniqueId() : row.id; - - return original; - } - - }; - - - // ********************************************* - // Init control - // ********************************************* - - $scope.initControl = function (control, index) { - control.$index = index; - control.$uniqueId = $scope.setUniqueId(); - - //error handling in case of missing editor.. - //should only happen if stripped earlier - if (!control.editor) { - control.$editorPath = "views/propertyeditors/grid/editors/error.html"; - } - - if (!control.$editorPath) { - var editorConfig = $scope.getEditor(control.editor.alias); - - if (editorConfig) { - control.editor = editorConfig; - - //if its an absolute path - if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) { - control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view); + cancelMove = true; } else { - //use convention - control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html"; + if (event.target.getScope_HackForSortable().area.controls.length == 0) { + + $scope.$apply(function () { + event.target.getScope_HackForSortable().area.dropOnEmpty = true; + }); + ui.placeholder.hide(); + } else { + ui.placeholder.show(); + } + cancelMove = false; + } + }, + + out: function (event, ui) { + $scope.$apply(function () { + var dropArea = event.target.getScope_HackForSortable().area; + dropArea.dropNotAllowed = false; + dropArea.dropOnEmpty = false; + }); + }, + + update: function (event, ui) { + /* add all RTEs which are affected by the dragging */ + if (!ui.sender) { + if (cancelMove) { + ui.item.sortable.cancel(); + } + ui.item.parents(".umb-cell-content").find(".umb-rte").each(function (key, value) { + var v1 = value.id; + + if ($.inArray(v1, notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, v1); + } + }); + } + else { + $(event.target).find(".umb-rte").each(function () { + if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, $(this).attr("id")); + } + }); + } + currentForm.$setDirty(); + }, + + start: function (event, ui) { + //Get the starting area for reference + var area = event.target.getScope_HackForSortable().area; + startingArea = area; + + // fade out control when sorting + ui.item[0].style.display = "block"; + ui.item[0].style.opacity = "0.5"; + + // reset dragged RTE settings in case a RTE isn't dragged + draggedRteSettings = undefined; + ui.item[0].style.display = "block"; + ui.item.find(".umb-rte").each(function (key, value) { + notIncludedRte = []; + var rteId = value.id; + + var editors = _.findWhere(tinyMCE.editors, { id: rteId }); + + // save the dragged RTE settings + if (editors) { + draggedRteSettings = editors.settings; + + // remove the dragged RTE + tinyMCE.execCommand("mceRemoveEditor", false, rteId); + + } + + }); + }, + + stop: function (event, ui) { + // Fade in control when sorting stops + ui.item[0].style.opacity = "1"; + + ui.item.offsetParent().find(".umb-rte").each(function (key, value) { + var rteId = value.id; + if ($.inArray(rteId, notIncludedRte) < 0) { + // add all dragged's neighbouring RTEs in the new cell + notIncludedRte.splice(0, 0, rteId); + } + }); + + // reconstruct the dragged RTE (could be undefined when dragging something else than RTE) + if (draggedRteSettings !== undefined) { + tinyMCE.init(draggedRteSettings); + } + + _.forEach(notIncludedRte, function (id) { + // reset all the other RTEs + if (draggedRteSettings === undefined || id !== draggedRteSettings.id) { + var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings; + tinyMCE.execCommand("mceRemoveEditor", false, id); + tinyMCE.init(rteSettings); + } + }); + + $scope.$apply(function () { + + var cell = event.target.getScope_HackForSortable().area; + cell.hasActiveChild = hasActiveChild(cell, cell.controls); + cell.active = false; + }); + } + + }; + + $scope.toggleSortMode = function () { + $scope.sortMode = !$scope.sortMode; + if ($scope.sortMode) { + $scope.reorderKey = "general_reorderDone"; + } else { + $scope.reorderKey = "general_reorder"; + } + }; + + $scope.showReorderButton = function () { + if ($scope.model.value && $scope.model.value.sections) { + for (var i = 0; $scope.model.value.sections.length > i; i++) { + var section = $scope.model.value.sections[i]; + if (section.rows && section.rows.length > 0) { + return true; + } } } + }; + + // ********************************************* + // Add items overlay menu + // ********************************************* + $scope.openEditorOverlay = function (event, area, index, key) { + var title = ""; + localizationService.localize("grid_insertControl").then(function (value) { + title = value; + $scope.editorOverlay = { + view: "itempicker", + filter: area.$allowedEditors.length > 15, + title: title, + availableItems: area.$allowedEditors, + event: event, + show: true, + submit: function (model) { + if (model.selectedItem) { + $scope.addControl(model.selectedItem, area, index); + $scope.editorOverlay.show = false; + $scope.editorOverlay = null; + } + } + }; + }); + }; + + // ********************************************* + // Template management functions + // ********************************************* + + $scope.addTemplate = function (template) { + $scope.model.value = angular.copy(template); + + //default row data + _.forEach($scope.model.value.sections, function (section) { + $scope.initSection(section); + }); + }; + + + // ********************************************* + // Row management function + // ********************************************* + + $scope.clickRow = function (index, rows) { + rows[index].active = true; + }; + + $scope.clickOutsideRow = function (index, rows) { + rows[index].active = false; + }; + + function getAllowedLayouts(section) { + + var layouts = $scope.model.config.items.layouts; + + //This will occur if it is a new section which has been + // created from a 'template' + if (section.allowed && section.allowed.length > 0) { + return _.filter(layouts, function (layout) { + return _.indexOf(section.allowed, layout.name) >= 0; + }); + } else { - control.$editorPath = "views/propertyeditors/grid/editors/error.html"; + + + return layouts; } } + $scope.addRow = function (section, layout, isInit) { - }; + //copy the selected layout into the rows collection + var row = angular.copy(layout); + + // Init row value + row = $scope.initRow(row); + + // Push the new row + if (row) { + section.rows.push(row); + } + if (!isInit) { + currentForm.$setDirty(); + } + + $scope.showRowConfigurations = false; + + eventsService.emit("grid.rowAdded", { scope: $scope, element: $element, row: row }); + + }; + + $scope.removeRow = function (section, $index) { + if (section.rows.length > 0) { + section.rows.splice($index, 1); + $scope.currentRow = null; + $scope.openRTEToolbarId = null; + currentForm.$setDirty(); + } + + if (section.rows.length === 0) { + $scope.showRowConfigurations = true; + } + }; + + var shouldApply = function (item, itemType, gridItem) { + if (item.applyTo === undefined || item.applyTo === null || item.applyTo === "") { + return true; + } + + if (typeof (item.applyTo) === "string") { + return item.applyTo === itemType; + } + + if (itemType === "row") { + if (item.applyTo.row === undefined) { + return false; + } + if (item.applyTo.row === null || item.applyTo.row === "") { + return true; + } + var rows = item.applyTo.row.split(','); + return _.indexOf(rows, gridItem.name) !== -1; + } else if (itemType === "cell") { + if (item.applyTo.cell === undefined) { + return false; + } + if (item.applyTo.cell === null || item.applyTo.cell === "") { + return true; + } + var cells = item.applyTo.cell.split(','); + var cellSize = gridItem.grid.toString(); + return _.indexOf(cells, cellSize) !== -1; + } + } + + $scope.editGridItemSettings = function (gridItem, itemType) { + + placeHolder = "{0}"; + + var styles, config; + if (itemType === 'control') { + styles = null; + config = angular.copy(gridItem.editor.config.settings); + } else { + styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); }); + config = _.filter(angular.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); }); + } + + if (angular.isObject(gridItem.config)) { + _.each(config, function (cfg) { + var val = gridItem.config[cfg.key]; + if (val) { + cfg.value = stripModifier(val, cfg.modifier); + } + }); + } + + if (angular.isObject(gridItem.styles)) { + _.each(styles, function (style) { + var val = gridItem.styles[style.key]; + if (val) { + style.value = stripModifier(val, style.modifier); + } + }); + } + + $scope.gridItemSettingsDialog = {}; + $scope.gridItemSettingsDialog.view = "views/propertyeditors/grid/dialogs/config.html"; + $scope.gridItemSettingsDialog.title = "Settings"; + $scope.gridItemSettingsDialog.styles = styles; + $scope.gridItemSettingsDialog.config = config; + + $scope.gridItemSettingsDialog.show = true; + + $scope.gridItemSettingsDialog.submit = function (model) { + + var styleObject = {}; + var configObject = {}; + + _.each(model.styles, function (style) { + if (style.value) { + styleObject[style.key] = addModifier(style.value, style.modifier); + } + }); + _.each(model.config, function (cfg) { + if (cfg.value) { + configObject[cfg.key] = addModifier(cfg.value, cfg.modifier); + } + }); + + gridItem.styles = styleObject; + gridItem.config = configObject; + gridItem.hasConfig = gridItemHasConfig(styleObject, configObject); + + currentForm.$setDirty(); + + $scope.gridItemSettingsDialog.show = false; + $scope.gridItemSettingsDialog = null; + }; + + $scope.gridItemSettingsDialog.close = function (oldModel) { + $scope.gridItemSettingsDialog.show = false; + $scope.gridItemSettingsDialog = null; + }; + + }; + + function stripModifier(val, modifier) { + if (!val || !modifier || modifier.indexOf(placeHolder) < 0) { + return val; + } else { + var paddArray = modifier.split(placeHolder); + if (paddArray.length == 1) { + if (modifier.indexOf(placeHolder) === 0) { + return val.slice(0, -paddArray[0].length); + } else { + return val.slice(paddArray[0].length, 0); + } + } else { + if (paddArray[1].length === 0) { + return val.slice(paddArray[0].length); + } + return val.slice(paddArray[0].length, -paddArray[1].length); + } + } + } + + var addModifier = function (val, modifier) { + if (!modifier || modifier.indexOf(placeHolder) < 0) { + return val; + } else { + return modifier.replace(placeHolder, val); + } + }; + + function gridItemHasConfig(styles, config) { + + if (_.isEmpty(styles) && _.isEmpty(config)) { + return false; + } else { + return true; + } + + } + + // ********************************************* + // Area management functions + // ********************************************* + + $scope.clickCell = function (index, cells, row) { + cells[index].active = true; + row.hasActiveChild = true; + }; + + $scope.clickOutsideCell = function (index, cells, row) { + cells[index].active = false; + row.hasActiveChild = hasActiveChild(row, cells); + }; + + $scope.cellPreview = function (cell) { + if (cell && cell.$allowedEditors) { + var editor = cell.$allowedEditors[0]; + return editor.icon; + } else { + return "icon-layout"; + } + }; - gridService.getGridEditors().then(function (response) { - $scope.availableEditors = response.data; + // ********************************************* + // Control management functions + // ********************************************* + $scope.clickControl = function (index, controls, cell) { + controls[index].active = true; + cell.hasActiveChild = true; + }; - //Localize the grid editor names - angular.forEach($scope.availableEditors, function (value, key) { - //If no translation is provided, keep using the editor name from the manifest - if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { - localizationService.localize("grid_" + value.alias).then(function(v){ - value.name = v; + $scope.clickOutsideControl = function (index, controls, cell) { + controls[index].active = false; + cell.hasActiveChild = hasActiveChild(cell, controls); + }; + + function hasActiveChild(item, children) { + + var activeChild = false; + + for (var i = 0; children.length > i; i++) { + var child = children[i]; + + if (child.active) { + activeChild = true; + } + } + + if (activeChild) { + return true; + } + + } + + + var guid = (function () { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return function () { + return s4() + s4() + "-" + s4() + "-" + s4() + "-" + + s4() + "-" + s4() + s4() + s4(); + }; + })(); + + $scope.setUniqueId = function (cell, index) { + return guid(); + }; + + $scope.addControl = function (editor, cell, index, initialize) { + + initialize = (initialize !== false); + + var newControl = { + value: null, + editor: editor, + $initializing: initialize + }; + + if (index === undefined) { + index = cell.controls.length; + } + + newControl.active = true; + + //populate control + $scope.initControl(newControl, index + 1); + + cell.controls.push(newControl); + + eventsService.emit("grid.itemAdded", { scope: $scope, element: $element, cell: cell, item: newControl }); + + }; + + $scope.addTinyMce = function (cell) { + var rte = $scope.getEditor("rte"); + $scope.addControl(rte, cell); + }; + + $scope.getEditor = function (alias) { + return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; }); + }; + + $scope.removeControl = function (cell, $index) { + $scope.currentControl = null; + cell.controls.splice($index, 1); + }; + + $scope.percentage = function (spans) { + return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); + }; + + + $scope.clearPrompt = function (scopedObject, e) { + scopedObject.deletePrompt = false; + e.preventDefault(); + e.stopPropagation(); + }; + + $scope.togglePrompt = function (scopedObject) { + scopedObject.deletePrompt = !scopedObject.deletePrompt; + }; + + $scope.hidePrompt = function (scopedObject) { + scopedObject.deletePrompt = false; + }; + + $scope.toggleAddRow = function () { + $scope.showRowConfigurations = !$scope.showRowConfigurations; + }; + + + // ********************************************* + // Initialization + // these methods are called from ng-init on the template + // so we can controll their first load data + // + // intialization sets non-saved data like percentage sizing, allowed editors and + // other data that should all be pre-fixed with $ to strip it out on save + // ********************************************* + + // ********************************************* + // Init template + sections + // ********************************************* + $scope.initContent = function () { + var clear = true; + + //settings indicator shortcut + if (($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) { + $scope.hasSettings = true; + } + + //ensure the grid has a column value set, + //if nothing is found, set it to 12 + if (!$scope.model.config.items.columns) { + $scope.model.config.items.columns = 12; + } else if (angular.isString($scope.model.config.items.columns)) { + $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); + } + + if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) { + + if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) { + + //This will occur if it is an existing value, in which case + // we need to determine which layout was applied by looking up + // the name + // TODO: We need to change this to an immutable ID!! + + var found = _.find($scope.model.config.items.templates, function (t) { + return t.name === $scope.model.value.name; + }); + + if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) { + + //Cool, we've found the template associated with our current value with matching sections counts, now we need to + // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't + // allowed for this template based on the current config. + + _.each(found.sections, function (templateSection, index) { + angular.extend($scope.model.value.sections[index], angular.copy(templateSection)); + }); + + } + } + + _.forEach($scope.model.value.sections, function (section, index) { + + if (section.grid > 0) { + $scope.initSection(section); + + //we do this to ensure that the grid can be reset by deleting the last row + if (section.rows.length > 0) { + clear = false; + } + } else { + $scope.model.value.sections.splice(index, 1); + } + }); + } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) { + $scope.addTemplate($scope.model.config.items.templates[0]); + clear = false; + } + + if (clear) { + $scope.model.value = undefined; + } + }; + + $scope.initSection = function (section) { + section.$percentage = $scope.percentage(section.grid); + + section.$allowedLayouts = getAllowedLayouts(section); + + if (!section.rows || section.rows.length === 0) { + section.rows = []; + if (section.$allowedLayouts.length === 1) { + $scope.addRow(section, section.$allowedLayouts[0], true); + } + } else { + _.forEach(section.rows, function (row, index) { + if (!row.$initialized) { + var initd = $scope.initRow(row); + + //if init fails, remove + if (!initd) { + section.rows.splice(index, 1); + } else { + section.rows[index] = initd; + } + } + }); + + // if there is more than one row added - hide row add tools + $scope.showRowConfigurations = false; + } + }; + + + // ********************************************* + // Init layout / row + // ********************************************* + $scope.initRow = function (row) { + + //merge the layout data with the original config data + //if there are no config info on this, splice it out + var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; }); + + if (!original) { + return null; + } else { + //make a copy to not touch the original config + original = angular.copy(original); + original.styles = row.styles; + original.config = row.config; + original.hasConfig = gridItemHasConfig(row.styles, row.config); + + + //sync area configuration + _.each(original.areas, function (area, areaIndex) { + + + if (area.grid > 0) { + var currentArea = row.areas[areaIndex]; + + if (currentArea) { + area.config = currentArea.config; + area.styles = currentArea.styles; + area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config); + } + + //set editor permissions + if (!area.allowed || area.allowAll === true) { + area.$allowedEditors = $scope.availableEditors; + area.$allowsRTE = true; + } else { + area.$allowedEditors = _.filter($scope.availableEditors, function (editor) { + return _.indexOf(area.allowed, editor.alias) >= 0; + }); + + if (_.indexOf(area.allowed, "rte") >= 0) { + area.$allowsRTE = true; + } + } + + //copy over existing controls into the new areas + if (row.areas.length > areaIndex && row.areas[areaIndex].controls) { + area.controls = currentArea.controls; + + _.forEach(area.controls, function (control, controlIndex) { + $scope.initControl(control, controlIndex); + }); + + } else { + //if empty + area.controls = []; + + //if only one allowed editor + if (area.$allowedEditors.length === 1) { + $scope.addControl(area.$allowedEditors[0], area, 0, false); + } + } + + //set width + area.$percentage = $scope.percentage(area.grid); + area.$uniqueId = $scope.setUniqueId(); + + } else { + original.areas.splice(areaIndex, 1); + } + }); + + //replace the old row + original.$initialized = true; + + //set a disposable unique ID + original.$uniqueId = $scope.setUniqueId(); + + //set a no disposable unique ID (util for row styling) + original.id = !row.id ? $scope.setUniqueId() : row.id; + + return original; + } + + }; + + + // ********************************************* + // Init control + // ********************************************* + + $scope.initControl = function (control, index) { + control.$index = index; + control.$uniqueId = $scope.setUniqueId(); + + //error handling in case of missing editor.. + //should only happen if stripped earlier + if (!control.editor) { + control.$editorPath = "views/propertyeditors/grid/editors/error.html"; + } + + if (!control.$editorPath) { + var editorConfig = $scope.getEditor(control.editor.alias); + + if (editorConfig) { + control.editor = editorConfig; + + //if its an absolute path + if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) { + control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view); + } + else { + //use convention + control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html"; + } + } + else { + control.$editorPath = "views/propertyeditors/grid/editors/error.html"; + } + } + + + }; + + + gridService.getGridEditors().then(function (response) { + $scope.availableEditors = response.data; + + //Localize the grid editor names + angular.forEach($scope.availableEditors, function (value, key) { + //If no translation is provided, keep using the editor name from the manifest + if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { + localizationService.localize("grid_" + value.alias).then(function (v) { + value.name = v; + }); + } + }); + + $scope.contentReady = true; + + // ********************************************* + // Init grid + // ********************************************* + + eventsService.emit("grid.initializing", { scope: $scope, element: $element }); + + $scope.initContent(); + + eventsService.emit("grid.initialized", { scope: $scope, element: $element }); + + }); + + //Clean the grid value before submitting to the server, we don't need + // all of that grid configuration in the value to be stored!! All of that + // needs to be merged in at runtime to ensure that the real config values are used + // if they are ever updated. + + var unsubscribe = $scope.$on("formSubmitting", function (e, args) { + + if (args.action === "save" && $scope.model.value && $scope.model.value.sections) { + _.each($scope.model.value.sections, function (section) { + if (section.rows) { + _.each(section.rows, function (row) { + if (row.areas) { + _.each(row.areas, function (area) { + + //Remove the 'editors' - these are the allowed editors, these will + // be injected at runtime to this editor, it should not be persisted + + if (area.editors) { + delete area.editors; + } + + if (area.controls) { + _.each(area.controls, function (control) { + if (control.editor) { + //replace + var alias = control.editor.alias; + control.editor = { + alias: alias + }; + } + }); + } + }); + } + }); + } }); } }); - $scope.contentReady = true; - - // ********************************************* - // Init grid - // ********************************************* - - eventsService.emit("grid.initializing", { scope: $scope, element: $element }); - - $scope.initContent(); - - eventsService.emit("grid.initialized", { scope: $scope, element: $element }); + //when the scope is destroyed we need to unsubscribe + $scope.$on("$destroy", function () { + unsubscribe(); + }); }); - - //Clean the grid value before submitting to the server, we don't need - // all of that grid configuration in the value to be stored!! All of that - // needs to be merged in at runtime to ensure that the real config values are used - // if they are ever updated. - - var unsubscribe = $scope.$on("formSubmitting", function (e, args) { - - if (args.action === "save" && $scope.model.value && $scope.model.value.sections) { - _.each($scope.model.value.sections, function(section) { - if (section.rows) { - _.each(section.rows, function (row) { - if (row.areas) { - _.each(row.areas, function (area) { - - //Remove the 'editors' - these are the allowed editors, these will - // be injected at runtime to this editor, it should not be persisted - - if (area.editors) { - delete area.editors; - } - - if (area.controls) { - _.each(area.controls, function (control) { - if (control.editor) { - //replace - var alias = control.editor.alias; - control.editor = { - alias: alias - }; - } - }); - } - }); - } - }); - } - }); - } - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on("$destroy", function () { - unsubscribe(); - }); - - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index f0171e2d00..068a60462c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -1,6 +1,6 @@
- + -
+
- {{control.editor.name}} + {{control.editor.name}}
@@ -199,25 +199,24 @@
-
- - - -
+
+ + + +
-
+
- -
+
-
+
From e6720a175cbf9b45a09b4b059ae9a15fed1bb0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 11:36:50 +0100 Subject: [PATCH 13/31] changed notification auto remove time to 10 seconds --- .../common/services/notifications.service.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index 2575b05bb6..c123ac6cea 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -85,17 +85,17 @@ angular.module('umbraco.services') nArray.push(item); if(!item.sticky) { - $timeout(function() { - var found = _.find(nArray, function(i) { - return i.id === item.id; - }); - - if (found) { - var index = nArray.indexOf(found); - nArray.splice(index, 1); - } - - }, 7000); + $timeout( + function() { + var found = _.find(nArray, function(i) { + return i.id === item.id; + }); + if (found) { + var index = nArray.indexOf(found); + nArray.splice(index, 1); + } + } + , 10000); } return item; From 84e4189b45c519e703f3060c513e350c29f83ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 11:37:05 +0100 Subject: [PATCH 14/31] added margin and round corners to notifications --- .../less/components/notifications/umb-notifications.less | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less index 9e16eca414..7f04fef9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -22,7 +22,14 @@ font-size: 14px; border: none; position: relative; - margin-bottom: 0; + border-radius: 10px; + margin: 10px; + + .close { + top: 0; + right: -6px; + opacity: 0.4; + } } .umb-notifications__notification.-extra-padding { From 15d5c67cd3192ba65ff72ec4416ff7eeb8c3d22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 13:42:05 +0100 Subject: [PATCH 15/31] global text-color variables --- src/Umbraco.Web.UI.Client/src/less/variables.less | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 6400ae0321..55be161cf8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -112,7 +112,7 @@ @blueExtraDark: #1b264f;// added 2019 @blueLight: #ADD8E6; @blueNight: #162335;// added 2019 -@orange: #f79c37;// updated 2019 +//@orange: #f79c37;// updated 2019 @pink: #D93F4C;// #C3325F;// update 2019 @pinkLight: #f5c1bc;// added 2019 @pinkRedLight: #ff8a89;// added 2019 @@ -199,6 +199,16 @@ .turquoise{color: @turquoise;} .turquoise-d1{color: @turquoise-d1;} +.text-warning { + color: @orange; +} +.text-error { + color: @red; +} +.text-success { + color: @green; +} + //icon colors for tree icons .color-red, .color-red i{color: @red-d1 !important;} From 0cc6e51b7a9ae3999a9ddfdaa089cc20e7186821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 13:43:03 +0100 Subject: [PATCH 16/31] simplify controller + only show text when near limit + different text when exceeding limit --- .../textarea/textarea.controller.js | 24 +++----------- .../propertyeditors/textarea/textarea.html | 9 ++++-- .../textbox/textbox.controller.js | 31 +++---------------- .../propertyeditors/textbox/textbox.html | 9 ++++-- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 5 +-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 5 +-- .../Umbraco/config/lang/en_us.xml | 5 +-- 7 files changed, 31 insertions(+), 57 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js index 60d762738f..4934adb5dd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -13,25 +13,11 @@ function textAreaController($scope) { $scope.model.maxlength = false; if ($scope.model.config && $scope.model.config.maxChars) { $scope.model.maxlength = true; - if($scope.model.value == undefined) { - $scope.model.count = ($scope.model.config.maxChars * 1); - } else { - $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; - } } - - $scope.model.change = function() { - if ($scope.model.config && $scope.model.config.maxChars) { - if($scope.model.value == undefined) { - $scope.model.count = ($scope.model.config.maxChars * 1); - } else { - $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; - } - if($scope.model.count < 0) { - $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.maxChars * 1)); - $scope.model.count = 0; - } - } + + $scope.model.change = function () { + $scope.model.count = $scope.model.value.length; } + $scope.model.change(); } -angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController); \ No newline at end of file +angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 2fdca5d654..80da759fba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -7,9 +7,12 @@ {{textareaFieldForm.textarea.errorMsg}} -
- {{model.count}} - characters left +
+ characters left
+
+ Maximum {0} characters ({1} too many) +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index 734903e46c..4ab827db59 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -4,39 +4,18 @@ function textboxController($scope) { if (!$scope.model.config) { $scope.model.config = {}; } - - $scope.model.maxlength = false; - if ($scope.model.config && $scope.model.config.maxChars) { - $scope.model.maxlength = true; - } - + if (!$scope.model.config.maxChars) { // 500 is the maximum number that can be stored // in the database, so set it to the max, even // if no max is specified in the config $scope.model.config.maxChars = 500; } - - if ($scope.model.maxlength) { - if ($scope.model.value === undefined) { - $scope.model.count = ($scope.model.config.maxChars * 1); - } else { - $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; - } - } - + $scope.model.change = function () { - if ($scope.model.config && $scope.model.config.maxChars) { - if ($scope.model.value === undefined) { - $scope.model.count = ($scope.model.config.maxChars * 1); - } else { - $scope.model.count = ($scope.model.config.maxChars * 1) - $scope.model.value.length; - } - if ($scope.model.count < 0) { - $scope.model.value = $scope.model.value.substring(0, ($scope.model.config.maxChars * 1)); - $scope.model.count = 0; - } - } + $scope.model.count = $scope.model.value.length; } + $scope.model.change(); + } angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 6f6ed6bdf6..0e041e7f34 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -12,9 +12,12 @@ Required -
- {{model.count}} - characters left +
+ characters left
+
+ Maximum {0} characters ({1} too many) +
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 875948d4d2..11dcd6ac57 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1568,8 +1568,9 @@ Mange hilsner fra Umbraco robotten Ingen ordbog elementer at vælge imellem - - Karakterer tilbage + + >{0} tegn tilbage.]]> + {1} for mange.]]> Slettet indhold med Id: {0} Relateret til original "parent" med id: {1} diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index fa6f689845..d10bc6ea0c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2078,8 +2078,9 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - - characters left + + >{0} characters left.]]> + {1} too many.]]> Trashed content with Id: {0} related to original parent content with Id: {1} 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 6ff4d32f08..c04f639c3a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2090,8 +2090,9 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - - characters left + + >{0} characters left.]]> + {1} too many.]]> Trashed content with Id: {0} related to original parent content with Id: {1} From 286978647eb57371bddee1b58a4a0026da8bce2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 14:04:31 +0100 Subject: [PATCH 17/31] fix type mistake, plus correcting math. --- .../src/views/propertyeditors/textarea/textarea.html | 4 ++-- .../src/views/propertyeditors/textbox/textbox.html | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 80da759fba..e08668fb20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -8,10 +8,10 @@
- characters left + characters left
- Maximum {0} characters ({1} too many) + Maximum {0} characters ({1} too many)
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 0e041e7f34..8385c951b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -13,10 +13,10 @@
- characters left + characters left
- Maximum {0} characters ({1} too many) + Maximum {0} characters ({1} too many)
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 11dcd6ac57..52be26e2fc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1569,7 +1569,7 @@ Mange hilsner fra Umbraco robotten Ingen ordbog elementer at vælge imellem - >{0} tegn tilbage.]]> + {0} tegn tilbage.]]> {1} for mange.]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d10bc6ea0c..218812270d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2079,7 +2079,7 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - >{0} characters left.]]> + {0} characters left.]]> {1} too many.]]> 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 c04f639c3a..9f578da1b6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2091,7 +2091,7 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - >{0} characters left.]]> + {0} characters left.]]> {1} too many.]]> From 50ee60e8c35eb4b8f4ed7fcf666950c369eb1ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 14:09:29 +0100 Subject: [PATCH 18/31] correcting translate tokens to use % --- .../src/views/propertyeditors/textarea/textarea.html | 4 ++-- .../src/views/propertyeditors/textbox/textbox.html | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index e08668fb20..94935423e8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -8,10 +8,10 @@
- characters left + %0% characters left.
- Maximum {0} characters ({1} too many) + Maximum %0% characters, %1% too many.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 8385c951b1..6e8543de26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -13,10 +13,10 @@
- characters left + %0% characters left.
- Maximum {0} characters ({1} too many) + Maximum %0% characters, %1% too many.
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 52be26e2fc..b7a8196e92 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1569,8 +1569,8 @@ Mange hilsner fra Umbraco robotten Ingen ordbog elementer at vælge imellem - {0} tegn tilbage.]]> - {1} for mange.]]> + %0% tegn tilbage.]]> + %1% for mange.]]> Slettet indhold med Id: {0} Relateret til original "parent" med id: {1} diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 218812270d..ab3a281114 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2079,8 +2079,8 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - {0} characters left.]]> - {1} too many.]]> + %0% characters left.]]> + %1% too many.]]> Trashed content with Id: {0} related to original parent content with Id: {1} 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 9f578da1b6..9c66d1bef8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2091,8 +2091,8 @@ To manage your website, simply open the Umbraco back office and start adding con No Dictionary items to choose from - {0} characters left.]]> - {1} too many.]]> + %0% characters left.]]> + %1% too many.]]> Trashed content with Id: {0} related to original parent content with Id: {1} From 118f6c7e50c1619bfb6e1f24b5832829754fbe4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 25 Mar 2019 14:46:42 +0100 Subject: [PATCH 19/31] added the ability to watch tokens in localization service --- .../localization/localize.directive.js | 19 +++++++--- .../common/services/localization.service.js | 36 +++++++++++++------ .../propertyeditors/textarea/textarea.html | 4 +-- .../propertyeditors/textbox/textbox.html | 4 +-- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js index c3093eee9e..df3770056e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -32,16 +32,27 @@ angular.module("umbraco.directives") restrict: 'E', scope:{ key: '@', - tokens: '=' + tokens: '=', + watchTokens: '@' }, replace: true, link: function (scope, element, attrs) { var key = scope.key; - var tokens = scope.tokens ? scope.tokens : null; - localizationService.localize(key, tokens).then(function(value){ - element.html(value); + scope.text = ""; + + // A render function to be able to update tokens as values update. + function render() { + element.html(localizationService.tokenReplace(scope.text, scope.tokens || null)); + } + + localizationService.localize(key).then(function(value){ + scope.text = value; + render(); }); + if (scope.watchTokens === 'true') { + scope.$watch("tokens", render, true); + } } }; }) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 2a9afdfa94..ea2ad73263 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -43,16 +43,11 @@ angular.module('umbraco.services') var entry = dictionary[value]; if (entry) { - if (tokens) { - for (var i = 0; i < tokens.length; i++) { - entry = entry.replace("%" + i + "%", tokens[i]); - } - } - return entry; + return service.tokenReplace(entry, tokens); } return "[" + value + "]"; } - + var service = { // array to hold the localized resource string entries dictionary: [], @@ -127,7 +122,29 @@ angular.module('umbraco.services') } return value; }, - + + + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenReplace + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to replace tokens + * @param {String} value the text-string to manipulate + * @param {Array} tekens An array of tokens values + * @returns {String} Replaced test-string + */ + tokenReplace: function (text, tokens) { + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + text = text.replace("%" + i + "%", tokens[i]); + } + } + return text; + }, + + /** * @ngdoc method * @name umbraco.services.localizationService#localize @@ -146,8 +163,7 @@ angular.module('umbraco.services') */ localize: function (value, tokens) { return service.initLocalizedResources().then(function (dic) { - var val = _lookup(value, tokens, dic); - return val; + return _lookup(value, tokens, dic); }); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 94935423e8..1be37d92d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -8,10 +8,10 @@
- %0% characters left. + %0% characters left.
- Maximum %0% characters, %1% too many. + Maximum %0% characters, %1% too many.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 6e8543de26..92f02b9f5b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -13,10 +13,10 @@
- %0% characters left. + %0% characters left.
- Maximum %0% characters, %1% too many. + Maximum %0% characters, %1% too many.
From 64bae439bf7c735410bc3c95fe45dfb5e90762e3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 26 Mar 2019 08:44:03 +0100 Subject: [PATCH 20/31] Fix stuff from review --- src/Umbraco.Web/Security/MembershipHelper.cs | 24 ++++++++++---------- src/Umbraco.Web/UmbracoHelper.cs | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index c2d80257e1..bbcfc0760e 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -289,14 +289,14 @@ namespace Umbraco.Web.Security return MemberCache.GetByProviderKey(key); } - public virtual IEnumerable GetByProviderKey(IEnumerable keys) + public virtual IEnumerable GetByProviderKeys(IEnumerable keys) { - return keys?.Select(GetByProviderKey).WhereNotNull() ?? new IPublishedContent[0]; + return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty(); } - public virtual IEnumerable GetByProviderKey(params object[] keys) + public virtual IEnumerable GetByProviderKeys(params object[] keys) { - return keys?.Select(GetByProviderKey).WhereNotNull() ?? new IPublishedContent[0]; + return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty(); } public virtual IPublishedContent GetById(int memberId) @@ -304,14 +304,14 @@ namespace Umbraco.Web.Security return MemberCache.GetById(memberId); } - public virtual IEnumerable GetById(IEnumerable memberIds) + public virtual IEnumerable GetByIds(IEnumerable memberIds) { - return memberIds?.Select(GetById).WhereNotNull() ?? new IPublishedContent[0]; + return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty(); } - public virtual IEnumerable GetById(params int[] memberIds) + public virtual IEnumerable GetByIds(params int[] memberIds) { - return memberIds?.Select(GetById).WhereNotNull() ?? new IPublishedContent[0]; + return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty(); } public virtual IPublishedContent GetById(Guid memberId) @@ -319,14 +319,14 @@ namespace Umbraco.Web.Security return GetByProviderKey(memberId); } - public virtual IEnumerable GetById(IEnumerable memberIds) + public virtual IEnumerable GetByIds(IEnumerable memberIds) { - return GetByProviderKey(memberIds.OfType()); + return GetByProviderKeys(memberIds.OfType()); } - public virtual IEnumerable GetById(params Guid[] memberIds) + public virtual IEnumerable GetByIds(params Guid[] memberIds) { - return GetByProviderKey(memberIds.OfType()); + return GetByProviderKeys(memberIds.OfType()); } public virtual IPublishedContent GetByUsername(string username) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index e9fdfd73e5..80e3b95534 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -291,7 +291,7 @@ namespace Umbraco.Web public IEnumerable Member(IEnumerable ids) { - return MembershipHelper.GetById(ids); + return MembershipHelper.GetByIds(ids); } public IEnumerable Member(IEnumerable ids) @@ -301,7 +301,7 @@ namespace Umbraco.Web public IEnumerable Member(IEnumerable ids) { - return MembershipHelper.GetById(ids); + return MembershipHelper.GetByIds(ids); } public IEnumerable Member(IEnumerable ids) @@ -326,7 +326,7 @@ namespace Umbraco.Web public IEnumerable Member(params Guid[] ids) { - return MembershipHelper.GetById(ids); + return MembershipHelper.GetByIds(ids); } public IEnumerable Member(params Udi[] ids) From b36288b987cd37122da42f4b261b1ab5a90bcfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 Mar 2019 14:11:01 +0100 Subject: [PATCH 21/31] infinity editing dim-layer for umb-modelcolumn --- src/Umbraco.Web.UI.Client/src/less/modals.less | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 771be1bc2a..8e88774613 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -55,6 +55,16 @@ position: absolute;; } +.--notInFront .umb-modalcolumn::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + background: rgba(0,0,0,.4); +} + /* re-align loader */ .umb-modal .umb-loader-wrapper, .umb-modalcolumn .umb-loader-wrapper, .umb-dialog .umb-loader-wrapper{ position:relative; From 458e51096151a1578e02241d9949ae547ebfa7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 Mar 2019 14:57:13 +0100 Subject: [PATCH 22/31] remove double ;; --- src/Umbraco.Web.UI.Client/src/less/modals.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 771be1bc2a..f922d7d95a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -52,7 +52,7 @@ bottom: 0px; left: 0px; right: 0px; - position: absolute;; + position: absolute; } /* re-align loader */ From 4a14d45a64199673125e846d51f60b3e76a1216b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 Mar 2019 14:57:30 +0100 Subject: [PATCH 23/31] remove inline styling --- .../src/views/content/overlays/schedule.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html index 2bf290b752..42f6ae7b98 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html @@ -27,7 +27,7 @@ {{vm.variants[0].releaseDateFormatted}} - + Set date @@ -59,7 +59,7 @@ {{vm.variants[0].expireDateFormatted}} - + Set date @@ -123,7 +123,7 @@ {{variant.releaseDateFormatted}} - + Set date @@ -149,7 +149,7 @@ {{variant.expireDateFormatted}} - + Set date From d6be02aaecd83823c8b0578a6314a807a86859af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 26 Mar 2019 14:57:45 +0100 Subject: [PATCH 24/31] style set date input --- .../src/less/properties.less | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 916f1b5a3a..d3e69a2d79 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -16,6 +16,31 @@ border-left: 1px solid @gray-10; } +.date-wrapper__date .flatpickr-input > a { + + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + padding: 5px 15px; + box-sizing: border-box; + min-width: 200px; + + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: 3px; + + &:hover { + text-decoration: none; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + + localize { + text-decoration: none; + } + } +} + //------------------- HISTORY ------------------ .history { From 4d0d17eb1b856d5c8390bee5541ed6e87f04c4be Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 26 Mar 2019 15:11:05 +0100 Subject: [PATCH 25/31] Remove params overloads on MembershipHelper --- src/Umbraco.Web/Security/MembershipHelper.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index bbcfc0760e..cdf696c520 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -294,11 +294,6 @@ namespace Umbraco.Web.Security return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty(); } - public virtual IEnumerable GetByProviderKeys(params object[] keys) - { - return keys?.Select(GetByProviderKey).WhereNotNull() ?? Enumerable.Empty(); - } - public virtual IPublishedContent GetById(int memberId) { return MemberCache.GetById(memberId); @@ -309,11 +304,6 @@ namespace Umbraco.Web.Security return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty(); } - public virtual IEnumerable GetByIds(params int[] memberIds) - { - return memberIds?.Select(GetById).WhereNotNull() ?? Enumerable.Empty(); - } - public virtual IPublishedContent GetById(Guid memberId) { return GetByProviderKey(memberId); @@ -324,11 +314,6 @@ namespace Umbraco.Web.Security return GetByProviderKeys(memberIds.OfType()); } - public virtual IEnumerable GetByIds(params Guid[] memberIds) - { - return GetByProviderKeys(memberIds.OfType()); - } - public virtual IPublishedContent GetByUsername(string username) { return MemberCache.GetByUsername(username); From 719a4a8b6a50b829e7b8ce1166a5afaafdde436b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Mar 2019 08:29:04 +0100 Subject: [PATCH 26/31] adjusted size --- src/Umbraco.Web.UI.Client/src/less/properties.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index d3e69a2d79..d206b2a413 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -22,7 +22,7 @@ align-items: center; justify-content: center; font-weight: 700; - padding: 5px 15px; + padding: 4px 15px; box-sizing: border-box; min-width: 200px; From 9b13d8f5d77e978d869086c3bebab6d04bed2644 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 27 Mar 2019 10:28:49 +0100 Subject: [PATCH 27/31] Stop logging useless errors in UriExtensions --- src/Umbraco.Core/UriExtensions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 083ca90cc4..1198b26e0f 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -23,21 +23,21 @@ namespace Umbraco.Core /// /// /// There are some special routes we need to check to properly determine this: - /// + /// /// If any route has an extension in the path like .aspx = back office - /// + /// /// These are def 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 /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// + /// /// These are def front-end: /// /Umbraco/Surface = front-end /// /Umbraco/Api = front-end /// But if we've got this far we'll just have to assume it's front-end anyways. - /// + /// /// internal static bool IsBackOfficeRequest(this Uri url, string applicationPath, IGlobalSettings globalSettings) { @@ -152,9 +152,9 @@ namespace Umbraco.Core var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"}; return toInclude.Any(ext.InvariantEquals) == false; } - catch (ArgumentException ex) + catch (ArgumentException) { - Current.Logger.Error(typeof(UriExtensions), ex, "Failed to determine if request was client side"); + Current.Logger.Debug(typeof(UriExtensions), "Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath); return false; } } From 8da9ecc9e4d0f967d9aea54674801b53ed13f85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 27 Mar 2019 12:49:03 +0100 Subject: [PATCH 28/31] date-wrapper styles for variants schedule publishing --- .../src/less/properties.less | 48 ++++++++++++++++++- .../src/views/content/overlays/schedule.html | 28 ++--------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index d206b2a413..e14bb5c0d6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -30,7 +30,51 @@ border: 1px dashed @ui-action-discreet-border; border-radius: 3px; - &:hover { + &:hover, &:focus { + text-decoration: none; + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + + localize { + text-decoration: none; + } + } +} + +//----- VARIANTS SCHEDULED PUBLISH ------ + +.date-wrapper-mini { + display: flex; + flex-direction: row; +} + +.date-wrapper-mini__date { + display: flex; + + margin-left: 5px; + margin-top: 5px; + margin-bottom: 10px; + + &:first-of-type { + margin-left: 0; + } +} + +.date-wrapper-mini__date .flatpickr-input > a { + + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + padding: 1px 15px; + box-sizing: border-box; + min-width: 180px; + + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: 3px; + + &:hover, &:focus { text-decoration: none; color: @ui-action-discreet-type-hover; border-color: @ui-action-discreet-border-hover; @@ -96,4 +140,4 @@ .history-line { display: none; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html index 42f6ae7b98..8cdc4a0abc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html @@ -84,8 +84,8 @@
-
- +
+
-
+
+
-
Publish:  {{variant.releaseDateFormatted}}
@@ -134,7 +134,7 @@
-
+
Unpublish:  {{variant.expireDateFormatted}}
@@ -182,24 +182,6 @@
-
-
-

-
- -
-
-
- {{ variant.language.name }} -
-
- - - -
-
-
-
-
From 6d9ed6c59bb6cb55d97d25c03195ff23054de391 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 28 Mar 2019 12:46:54 +0100 Subject: [PATCH 29/31] Change Member to Members (plural) for methods returning multiple members --- src/Umbraco.Web/UmbracoHelper.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 80e3b95534..1c6eb28b92 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -289,52 +289,52 @@ namespace Umbraco.Web return asInt ? MembershipHelper.GetById(asInt.Result) : MembershipHelper.GetByProviderKey(id); } - public IEnumerable Member(IEnumerable ids) + public IEnumerable Members(IEnumerable ids) { return MembershipHelper.GetByIds(ids); } - public IEnumerable Member(IEnumerable ids) + public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(IEnumerable ids) + public IEnumerable Members(IEnumerable ids) { return MembershipHelper.GetByIds(ids); } - public IEnumerable Member(IEnumerable ids) + public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(IEnumerable ids) + public IEnumerable Members(IEnumerable ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(params int[] ids) + public IEnumerable Members(params int[] ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(params string[] ids) + public IEnumerable Members(params string[] ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(params Guid[] ids) + public IEnumerable Members(params Guid[] ids) { return MembershipHelper.GetByIds(ids); } - public IEnumerable Member(params Udi[] ids) + public IEnumerable Members(params Udi[] ids) { return ids.Select(Member).WhereNotNull(); } - public IEnumerable Member(params object[] ids) + public IEnumerable Members(params object[] ids) { return ids.Select(Member).WhereNotNull(); } From 07bb7ac0f7c9bebf57b0ba31d650d76ee896b217 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 29 Mar 2019 08:26:36 +0100 Subject: [PATCH 30/31] Add content.SetValue overload for posted files --- src/Umbraco.Core/ContentExtensions.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index f802c2d0d9..a7d40b0b7d 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -7,6 +7,7 @@ using System.Web; using System.Xml.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NPoco.Expressions; using Umbraco.Core.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -52,8 +53,8 @@ namespace Umbraco.Core return ContentStatus.Unpublished; } - - + + #endregion /// @@ -134,9 +135,14 @@ namespace Umbraco.Core /// /// Sets the posted file value of a property. /// - /// This really is for FileUpload fields only, and should be obsoleted. For anything else, - /// you need to store the file by yourself using Store and then figure out - /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. + public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, HttpPostedFileBase postedFile, string culture = null, string segment = null) + { + content.SetValue(contentTypeBaseServiceProvider, propertyTypeAlias, postedFile.FileName, postedFile.InputStream, culture, segment); + } + + /// + /// Sets the posted file value of a property. + /// public static void SetValue(this IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { if (filename == null || filestream == null) return; From cea8753ff28a3b6c695ffd10e183c9f4e6fb5e45 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 28 Mar 2019 12:10:59 +0100 Subject: [PATCH 31/31] Replace dynamic ViewBag with ViewData --- .../Umbraco/Install/Views/Index.cshtml | 6 +- .../Umbraco/Views/AuthorizeUpgrade.cshtml | 4 +- .../Umbraco/Views/Default.cshtml | 4 +- .../Editors/BackOfficeController.cs | 37 ++++------ .../Install/Controllers/InstallController.cs | 4 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/ViewDataExtensions.cs | 71 +++++++++++++++++++ 7 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 src/Umbraco.Web/ViewDataExtensions.cs diff --git a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml index 1d397bfd01..e74dbea217 100644 --- a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml @@ -7,7 +7,7 @@ - + @@ -66,8 +66,8 @@ var Umbraco = {}; Umbraco.Sys = {}; Umbraco.Sys.ServerVariables = { - "installApiBaseUrl": "@ViewBag.InstallApiBaseUrl", - "umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder" + "installApiBaseUrl": "@ViewData.GetInstallApiBaseUrl()", + "umbracoBaseUrl": "@ViewData.GetUmbracoBaseFolder()" }; diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index fba9a83789..b7c1e6540c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -51,7 +51,7 @@ @{ var externalLoginUrl = Url.Action("ExternalLogin", "BackOffice", new { - area = ViewBag.UmbracoPath, + area = ViewData.GetUmbracoPath(), //Custom redirect URL since we don't want to just redirect to the back office since this is for authing upgrades redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice") }); @@ -61,7 +61,7 @@