From 45f574ed3a6c90137d1e0a18b8a3188b30685339 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:27:39 +0200 Subject: [PATCH 001/278] Ensure the correct color for items in the Nested Content item picker --- .../less/components/umb-nested-content.less | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 514a73407c..e433b9fe4a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -208,11 +208,25 @@ width: 99%; } -.usky-grid.umb-nested-content__node-type-picker .cell-tools-menu { - position: relative; - transform: translate(-50%, -25%); -} +.usky-grid.umb-nested-content__node-type-picker { + .cell-tools-menu { + position: relative; + transform: translate(-50%, -25%); + } + .elements li { + &:hover { + i { + color: @white !important; + } + } + + i { + // make sure the item icons shown are in the correct color according to their doc type icon instead of the grid editor item color + color: unset; + } + } +} // this resolves the layout issue introduced in nested content in 7.12 with the addition of the input for link anchors // the attribute selector ensures the change only applies to the linkpicker overlay From c6e2ced971e3fefba29c0082be9c29431fe98984 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:30:27 +0200 Subject: [PATCH 002/278] Fix Nested Content "second time dragging" issue --- .../src/less/components/umb-nested-content.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 514a73407c..31ebe15d8a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -1,5 +1,6 @@ .umb-nested-content { text-align: center; + position: relative; } .umb-nested-content--not-supported { From a7573ce31f1f166f86c821ddbbb169383c904281 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 07:35:16 +0200 Subject: [PATCH 003/278] Avoid data loss when dragging an unsaved Nested Content item (#3014) --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 0a44a9fcaa..c527cc50a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -231,6 +231,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop cursor: "move", handle: ".umb-nested-content__icon--move", start: function (ev, ui) { + updateModel(); // Yea, yea, we shouldn't modify the dom, sue me $("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () { tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id')); From 0dbbb585ad2c76647ae151403e3cb3d81c8ac254 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 29 Sep 2018 17:06:04 +0200 Subject: [PATCH 004/278] Fix loading issue for RTE's in Nested Content --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 7 +++++++ .../src/views/propertyeditors/rte/rte.html | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index fabe7c2291..4805c820ee 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -115,6 +115,13 @@ div.umb-codeeditor .umb-btn-toolbar { // // RTE // -------------------------------------------------- +.umb-rte { + position: relative; + + .-loading { + position: absolute; + } +} .mce-tinymce{border: 1px solid @gray-8 !important; border-radius: 0px !important;} .mce-panel{background: @gray-10 !important; border-color: @gray-8 !important;} .mce-btn-group, .mce-btn{border: none !important; background: none !important;} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index 774f860c31..b9c2364925 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -1,5 +1,5 @@
-
Loading...
+
Loading...
From 2c9256c6724d2e840b0b267657a3db930f00e225 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 30 Sep 2018 18:50:33 +0200 Subject: [PATCH 005/278] Don't let the same doctype be selectable twice in the Nested Content configuration --- .../nestedcontent/nestedcontent.controller.js | 12 ++++++++++++ .../nestedcontent/nestedcontent.doctypepicker.html | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 0a44a9fcaa..7be141362e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -37,6 +37,18 @@ }); }); + $scope.selectableDocTypesFor = function (config) { + // return all doctypes that are: + // 1. either already selected for this config, or + // 2. not selected in any other config + return _.filter($scope.model.docTypes, function (docType) { + return docType.alias === config.ncAlias || !_.find($scope.model.value, function(c) { + return docType.alias === c.ncAlias; + }); + }); + + } + if (!$scope.model.value) { $scope.model.value = []; $scope.add(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html index 0617331682..e0a16d8687 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html @@ -22,7 +22,7 @@ From eda6e084a78f88afd53802bf42cad658f71e71e3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 1 Oct 2018 14:37:40 +0200 Subject: [PATCH 006/278] Fix casing issue in member group service/repo --- .../Repositories/MemberGroupRepository.cs | 2 +- .../Services/MemberServiceTests.cs | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 404c32640a..8b5dbb435e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -284,7 +284,7 @@ namespace Umbraco.Core.Persistence.Repositories var nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase); foreach (var toAssign in nonAssignedRoles) { - var groupId = rolesForNames.First(x => x.Text == toAssign).NodeId; + var groupId = rolesForNames.First(x => x.Text.InvariantEquals(toAssign)).NodeId; Database.Insert(new Member2MemberGroupDto { Member = mId, MemberGroup = groupId }); } } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 4ba3866c64..3ee5a4804e 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -288,6 +288,29 @@ namespace Umbraco.Tests.Services Assert.AreEqual(2, membersInRole.Count()); } + [Test] + public void Associate_Members_To_Roles_With_Member_Id_Casing() + { + ServiceContext.MemberService.AddRole("MyTestRole1"); + + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + var member1 = MockedMember.CreateSimpleMember(memberType, "test1", "test1@test.com", "pass", "test1"); + ServiceContext.MemberService.Save(member1); + var member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2"); + ServiceContext.MemberService.Save(member2); + + // temp make sure they exist + Assert.IsNotNull(ServiceContext.MemberService.GetById(member1.Id)); + Assert.IsNotNull(ServiceContext.MemberService.GetById(member2.Id)); + + ServiceContext.MemberService.AssignRoles(new[] { member1.Id, member2.Id }, new[] { "mytestrole1" }); + + var membersInRole = ServiceContext.MemberService.GetMembersInRole("MyTestRole1"); + + Assert.AreEqual(2, membersInRole.Count()); + } + [Test] public void Associate_Members_To_Roles_With_Member_Username() { @@ -1179,4 +1202,4 @@ namespace Umbraco.Tests.Services } } -} \ No newline at end of file +} From 66221364ca7fd489b28ab44135c8c9174cc072cf Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 1 Oct 2018 21:21:17 +0200 Subject: [PATCH 007/278] Hide "Created Date" field when the media hasn't yet been created --- .../src/views/components/media/umb-media-node-info.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 19095acb90..7cfcb835a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -29,11 +29,11 @@ - + {{node.createDateFormatted}} by {{ node.owner.name }} - + {{node.updateDateFormatted}} @@ -47,7 +47,7 @@ - +
{{ node.id }}
{{ node.key }}
From 9c04abc5ad7eef49ac7b3d829083832f3284667b Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Mon, 1 Oct 2018 21:40:24 +0200 Subject: [PATCH 008/278] Hide the "Links" box when there are no links --- .../src/views/components/content/umb-content-node-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 0706e9596c..7085babf59 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -2,7 +2,7 @@
- +
From 0a245dda4358206df1d4da3c79541201eb21b888 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 2 Oct 2018 19:54:12 +0200 Subject: [PATCH 019/278] Add an outer check to make sure the scope.includeSubFolders property is not undefined, which makes the media render in the view again --- .../components/umbmediagrid.directive.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 9aa4965022..d31ef61150 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -125,13 +125,19 @@ Use this directive to generate a thumbnail grid of media items. i--; } - if (scope.includeSubFolders !== 'true') { - if (item.parentId !== parseInt(scope.currentFolderId)) { - scope.items.splice(i, 1); - i--; + + // If subfolder search is not enabled remove the media items that's not needed + // Make sure that includeSubFolder is not undefined since the directive is used + // in contexts where it should not be used. Currently only used when we trigger + // a media picker + if(scope.includeSubFolders !== undefined){ + if (scope.includeSubFolders !== 'true') { + if (item.parentId !== parseInt(scope.currentFolderId)) { + scope.items.splice(i, 1); + i--; + } } } - } @@ -152,7 +158,7 @@ Use this directive to generate a thumbnail grid of media items. } if (!item.isFolder) { - + // handle entity if(item.image) { item.thumbnail = mediaHelper.resolveFileFromEntity(item, true); @@ -161,7 +167,7 @@ Use this directive to generate a thumbnail grid of media items. } else { item.thumbnail = mediaHelper.resolveFile(item, true); item.image = mediaHelper.resolveFile(item, false); - + var fileProp = _.find(item.properties, function (v) { return (v.alias === "umbracoFile"); }); From a8df8f484ff7eb8e03584f3691bf9b9001ba9849 Mon Sep 17 00:00:00 2001 From: KimHolzmann Date: Wed, 3 Oct 2018 18:13:35 +0200 Subject: [PATCH 020/278] #3022 media uploader in rte doesn't select uploaded image (#3117) --- .../common/overlays/mediaPicker/mediapicker.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 60c89197a9..7c43f5909f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -237,7 +237,10 @@ angular.module("umbraco") $scope.onUploadComplete = function(files) { $scope.gotoFolder($scope.currentFolder).then(function() { if (files.length === 1 && $scope.model.selectedImages.length === 0) { - selectImage($scope.images[$scope.images.length - 1]); + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); } }); }; From c27b9d76dbb0c72a700a6af13f4b50c50994a4b8 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 3 Oct 2018 08:09:39 +0200 Subject: [PATCH 021/278] fixes #3126 - If no property data is found for a specific version - a null exception is thrown. - Throwing an exception with a description instead of hitting a null exception. --- .../Repositories/ContentRepository.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 6c00964c15..4f19dc9241 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) .InnerJoin() .On(left => left.NodeId, right => right.ContentTypeId); //TODO: IF we want to enable querying on content type information this will need to be joined @@ -836,19 +836,19 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", } public int CountPublished(string contentTypeAlias = null) - { - if (contentTypeAlias.IsNullOrWhiteSpace()) - { + { + if (contentTypeAlias.IsNullOrWhiteSpace()) + { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); + .Where(x => x.Published == true); + return Database.ExecuteScalar(sql); } - else - { + else + { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true) - .Where(x => x.Alias == contentTypeAlias); - return Database.ExecuteScalar(sql); + .Where(x => x.Published == true) + .Where(x => x.Alias == contentTypeAlias); + return Database.ExecuteScalar(sql); } } @@ -1220,7 +1220,14 @@ ORDER BY cmsContentVersion.id DESC if (def.DocumentDto.TemplateId.HasValue) templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null cc.Template = template; - cc.Properties = propertyData[cc.Version]; + if (propertyData.ContainsKey(cc.Version)) + { + cc.Properties = propertyData[cc.Version]; + } + else + { + throw new InvalidOperationException($"No property data found for version: '{cc.Version}'."); + } //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -1307,4 +1314,4 @@ ORDER BY cmsContentVersion.id DESC } } } -} \ No newline at end of file +} From 9579b97dde0aca64f5aeaed76d291661bf54141c Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 3 Oct 2018 08:01:26 +0200 Subject: [PATCH 022/278] fixes #3124 - logging in BaseDataCreation. --- .../Persistence/DatabaseSchemaHelper.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index f03e751427..304cdc185f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -117,6 +117,8 @@ namespace Umbraco.Core.Persistence var tableExist = TableExist(tableName); if (overwrite && tableExist) { + _logger.Info(string.Format("Table '{0}' already exists, but will be recreated", tableName)); + DropTable(tableName); tableExist = false; } @@ -169,13 +171,22 @@ namespace Umbraco.Core.Persistence _logger.Info(string.Format("Create Foreign Key sql {0}:\n {1}", createdFk, sql)); } - - transaction.Complete(); + if (overwrite) + { + _logger.Info(string.Format("Table '{0}' was recreated", tableName)); + } + else + { + _logger.Info(string.Format("New table '{0}' was created", tableName)); + } } } - - _logger.Info(string.Format("New table '{0}' was created", tableName)); + else + { + // The table exists and was not recreated/overwritten. + _logger.Info(string.Format("Table '{0}' already exists - no changes were made", tableName)); + } } public void DropTable() From 26759e97faabb065adfc998ad48e59ed0a10f4c4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Oct 2018 19:03:22 +0200 Subject: [PATCH 023/278] First iteration to populate property type changes when they are changed variant/invariant --- .../Components/RelateOnCopyComponent.cs | 2 + .../UpdateContentOnVariantChangesComponent.cs | 93 +++++++++++++++++++ src/Umbraco.Core/Models/ContentTypeBase.cs | 3 +- .../Models/ContentTypeBaseExtensions.cs | 49 +++++++++- .../Implement/ContentRepositoryBase.cs | 3 + .../Implement/ContentTypeRepositoryBase.cs | 4 +- .../Implement/LanguageRepository.cs | 23 +++-- .../ContentTypeServiceBaseOfTItemTService.cs | 6 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 20 +++- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 5 +- ...itoryTest.cs => DocumentRepositoryTest.cs} | 2 +- .../Services/ContentTypeServiceTests.cs | 49 ++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../NuCache/PublishedSnapshotService.cs | 15 +-- .../XmlPublishedCache/XmlStore.cs | 12 +-- 16 files changed, 253 insertions(+), 36 deletions(-) create mode 100644 src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs rename src/Umbraco.Tests/Persistence/Repositories/{ContentRepositoryTest.cs => DocumentRepositoryTest.cs} (99%) diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 4ebd309e9f..5356fa6e30 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -1,10 +1,12 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { + //TODO: This should just exist in the content service/repo! [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent diff --git a/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs b/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs new file mode 100644 index 0000000000..06d2b86166 --- /dev/null +++ b/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.Components +{ + /// + /// Manages data changes for when content/property types have variation changes + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public sealed class UpdateContentOnVariantChangesComponent : UmbracoComponentBase, IUmbracoCoreComponent + { + private IContentService _contentService; + private ILocalizationService _langService; + + public void Initialize(IContentService contentService, ILocalizationService langService) + { + _contentService = contentService; + _langService = langService; + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + } + + private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs e) + { + var defaultLang = _langService.GetDefaultLanguageIsoCode(); //this will be cached + + foreach (var c in e.Changes) + { + // existing property alias change? + var hasAnyPropertyVariationChanged = c.Item.WasPropertyTypeVariationChanged(out var aliases); + if (hasAnyPropertyVariationChanged) + { + var contentOfType = _contentService.GetByType(c.Item.Id); + + foreach (var a in aliases) + { + var propType = c.Item.PropertyTypes.First(x => x.Alias == a); + + switch(propType.Variations) + { + case ContentVariation.Culture: + //if the current variation is culture it means that the previous was nothing + foreach (var content in contentOfType) + { + object invariantVal; + try + { + content.Properties[a].PropertyType.Variations = ContentVariation.Nothing; + //now get the culture val + invariantVal = content.GetValue(a); + } + finally + { + content.Properties[a].PropertyType.Variations = ContentVariation.Culture; + } + //set the invariant value + content.SetValue(a, invariantVal, defaultLang); + } + break; + case ContentVariation.Nothing: + //if the current variation is nothing it means that the previous was culture + foreach(var content in contentOfType) + { + object cultureVal; + try + { + content.Properties[a].PropertyType.Variations = ContentVariation.Culture; + //now get the culture val + cultureVal = content.GetValue(a, defaultLang); + } + finally + { + content.Properties[a].PropertyType.Variations = ContentVariation.Nothing; + } + //set the invariant value + content.SetValue(a, cultureVal); + } + break; + default: + throw new NotSupportedException("The variation change is not supported"); + } + } + + _contentService.Save(contentOfType); + } + } + } + } +} diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 6f90b5201d..7467d83099 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -408,7 +408,8 @@ namespace Umbraco.Core.Models /// PropertyTypes that are not part of a PropertyGroup /// [IgnoreDataMember] - internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; + //fixme should we mark this as EditorBrowsable hidden since it really isn't ever used? + internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; /// /// Indicates whether a specific property on the current entity is dirty. diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index ef55f0d469..8af48bb881 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -1,4 +1,8 @@ -using Umbraco.Core.Models.PublishedContent; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Models { @@ -16,5 +20,48 @@ namespace Umbraco.Core.Models else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member; return itemType; } + + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType) + { + return contentType.WasPropertyTypeVariationChanged(out var _); + } + + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection aliases) + { + var a = new List(); + + // property variation change? + var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType => + { + if (!(propertyType is IRememberBeingDirty dirtyProperty)) + throw new Exception("oops"); + + // skip new properties + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewProperty = dirtyProperty.WasPropertyDirty("Id"); + if (isNewProperty) return false; + + // variation change? + var dirty = dirtyProperty.WasPropertyDirty("Variations"); + if (dirty) + a.Add(propertyType.Alias); + + return dirty; + + }); + + aliases = a; + return hasAnyPropertyVariationChanged; + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 6ace73fbc3..c258a76b30 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -608,6 +608,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region UnitOfWork Events + //fixme: The reason these events are in the repository is for legacy, the events should exist at the service + // level now since we can fire these events within the transaction... so move the events to service level + public class ScopedEntityEventArgs : EventArgs { public ScopedEntityEventArgs(IScope scope, TEntity entity) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 555fc56157..05b9bc8631 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -323,9 +323,7 @@ AND umbracoNode.id <> @id", }); } - // fixme below, manage the property type - - // delete ??? fixme wtf is this? + // delete property types // ... by excepting entries from db with entries from collections if (entity.IsPropertyDirty("PropertyTypes") || entity.PropertyTypes.Any(x => x.IsDirty())) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index ae5d9ae8b8..2026daba74 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false); } - private FullDataSetRepositoryCachePolicy TypedCachePolicy => (FullDataSetRepositoryCachePolicy) CachePolicy; + private FullDataSetRepositoryCachePolicy TypedCachePolicy => CachePolicy as FullDataSetRepositoryCachePolicy; #region Overrides of RepositoryBase @@ -215,7 +215,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public ILanguage GetByIsoCode(string isoCode) { - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + var id = GetIdByIsoCode(isoCode, throwOnNotFound: false); return id.HasValue ? Get(id.Value) : null; } @@ -228,7 +231,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { if (isoCode == null) return null; - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + lock (_codeIdMap) { if (_codeIdMap.TryGetValue(isoCode, out var id)) return id; @@ -246,7 +252,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { if (id == null) return null; - TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way + // ensure cache is populated, in a non-expensive way + if (TypedCachePolicy != null) + TypedCachePolicy.GetAllCached(PerformGetAll); + lock (_codeIdMap) // yes, we want to lock _codeIdMap { if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode; @@ -269,8 +278,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // do NOT leak that language, it's not deep-cloned! private ILanguage GetDefault() { - // get all cached, non-cloned - var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList(); + // get all cached + var languages = (TypedCachePolicy?.GetAllCached(PerformGetAll) //try to get all cached non-cloned if using the correct cache policy (not the case in unit tests) + ?? CachePolicy.GetAll(Array.Empty(), PerformGetAll)).ToList(); + var language = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage); if (language != null) return language; diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index 9fa9a47003..33fb9a0894 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -20,12 +20,12 @@ namespace Umbraco.Core.Services.Implement internal static event TypedEventHandler.EventArgs> Changed; // that one is always immediate (transactional) - public static event TypedEventHandler.EventArgs> UowRefreshedEntity; + public static event TypedEventHandler.EventArgs> ScopedRefreshedEntity; // used by tests to clear events internal static void ClearScopeEvents() { - UowRefreshedEntity = null; + ScopedRefreshedEntity = null; } // these must be dispatched @@ -48,7 +48,7 @@ namespace Umbraco.Core.Services.Implement protected void OnUowRefreshedEntity(ContentTypeChange.EventArgs args) { // that one is always immediate (not dispatched, transactional) - UowRefreshedEntity.RaiseEvent(args, This); + ScopedRefreshedEntity.RaiseEvent(args, This); } protected bool OnSavingCancelled(IScope scope, SaveEventArgs args) diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 8ed0a0f645..a3bc87315b 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -118,6 +118,8 @@ namespace Umbraco.Core.Services.Implement // - content type alias changed // - content type property removed, or alias changed // - content type composition removed (not testing if composition had properties...) + // - content type variation changed + // - property type variation changed // // because these are the changes that would impact the raw content data @@ -132,7 +134,8 @@ namespace Umbraco.Core.Services.Implement var dirty = (IRememberBeingDirty)contentType; // skip new content types - var isNewContentType = dirty.WasPropertyDirty("HasIdentity"); + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewContentType = dirty.WasPropertyDirty("Id"); if (isNewContentType) { AddChange(changes, contentType, ContentTypeChangeTypes.Create); @@ -149,12 +152,12 @@ namespace Umbraco.Core.Services.Implement throw new Exception("oops"); // skip new properties - var isNewProperty = dirtyProperty.WasPropertyDirty("HasIdentity"); + //TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewProperty = dirtyProperty.WasPropertyDirty("Id"); if (isNewProperty) return false; // alias change? - var hasPropertyAliasBeenChanged = dirtyProperty.WasPropertyDirty("Alias"); - return hasPropertyAliasBeenChanged; + return dirtyProperty.WasPropertyDirty("Alias"); }); // removed properties? @@ -163,8 +166,15 @@ namespace Umbraco.Core.Services.Implement // removed compositions? var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved"); + // variation changed? + var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations"); + + // property variation change? + var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged(); + // main impact on properties? - var hasPropertyMainImpact = hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; + var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged + || hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; if (hasAliasChanged || hasPropertyMainImpact) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 496a66137a..379d5ad48a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -170,6 +170,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 31105aa28e..6087279285 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -63,8 +63,7 @@ namespace Umbraco.Tests.Persistence.Repositories } //TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). - - + [Test] public void Maps_Templates_Correctly() { @@ -377,7 +376,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.Save(contentType); - var dirty = ((ICanBeDirty)contentType).IsDirty(); + var dirty = contentType.IsDirty(); // Assert Assert.That(contentType.HasIdentity, Is.True); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs similarity index 99% rename from src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs rename to src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 7ff7c0a2e4..68e29c4efe 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class ContentRepositoryTest : TestWithDatabaseBase + public class DocumentRepositoryTest : TestWithDatabaseBase { public override void SetUp() { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index be07dbdd23..8ece9621ce 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Core.Components; namespace Umbraco.Tests.Services { @@ -23,6 +24,54 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase { + [Test] + public void Change_Property_Type_From_Variant_Invariant() + { + //initialize the listener which is responsible for updating content based on variant/invariant changes + var listener = new UpdateContentOnVariantChangesComponent(); + listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); + + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + contentType.ResetDirtyProperties(false); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + } + [Test] public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin() { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a3f02b2fdc..aac1f0b865 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -123,6 +123,7 @@ + @@ -393,7 +394,6 @@ - diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 8786753e4f..a9903669b9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -198,6 +198,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void InitializeRepositoryEvents() { + //fixme: The reason these events are in the repository is for legacy, the events should exist at the service + // level now since we can fire these events within the transaction... so move the events to service level + // plug repository event handlers // these trigger within the transaction to ensure consistency // and are used to maintain the central, database-level XML cache @@ -212,9 +215,9 @@ namespace Umbraco.Web.PublishedCache.NuCache MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; // plug - ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; } private void TearDownRepositoryEvents() @@ -229,9 +232,9 @@ namespace Umbraco.Web.PublishedCache.NuCache //MemberRepository.RemovedVersion -= OnMemberRemovedVersion; MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; - ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; } public override void Dispose() diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 5fa89e3f7b..5b816c2f26 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -194,9 +194,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; // plug - ContentTypeService.UowRefreshedEntity += OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity += OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity += OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; _withRepositoryEvents = true; } @@ -213,9 +213,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache MemberRepository.ScopeVersionRemove -= OnMemberRemovingVersion; MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; - ContentTypeService.UowRefreshedEntity -= OnContentTypeRefreshedEntity; - MediaTypeService.UowRefreshedEntity -= OnMediaTypeRefreshedEntity; - MemberTypeService.UowRefreshedEntity -= OnMemberTypeRefreshedEntity; + ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; _withRepositoryEvents = false; } From f76df08cd2df3c84453db4f16ba9b3d5ada697d0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Oct 2018 19:34:02 +0200 Subject: [PATCH 024/278] ensures that property type variations are set to nothing if the content type is updated to nothing --- .../UpdateContentOnVariantChangesComponent.cs | 7 +- .../Implement/ContentTypeRepositoryBase.cs | 9 ++ .../Services/ContentTypeServiceTests.cs | 99 +++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs b/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs index 06d2b86166..71fe4d46bd 100644 --- a/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs +++ b/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs @@ -12,6 +12,7 @@ namespace Umbraco.Core.Components /// Manages data changes for when content/property types have variation changes /// [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + //fixme: this one MUST fire before any of the content cache ones public sealed class UpdateContentOnVariantChangesComponent : UmbracoComponentBase, IUmbracoCoreComponent { private IContentService _contentService; @@ -40,7 +41,9 @@ namespace Umbraco.Core.Components { var propType = c.Item.PropertyTypes.First(x => x.Alias == a); - switch(propType.Variations) + //fixme: this does not take into account segments, but how can we since we don't know what it changed from? + + switch (propType.Variations) { case ContentVariation.Culture: //if the current variation is culture it means that the previous was nothing @@ -50,7 +53,7 @@ namespace Umbraco.Core.Components try { content.Properties[a].PropertyType.Variations = ContentVariation.Nothing; - //now get the culture val + //now get the invariant val invariantVal = content.GetValue(a); } finally diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 05b9bc8631..743b7c858e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -402,10 +402,19 @@ AND umbracoNode.id <> @id", propertyType.PropertyGroupId = new Lazy(() => groupId); } + //check if the content type variation has been changed to Nothing since we will need to update + //all property types to Nothing as well if they aren't already + //fixme: this does not take into account segments, but how can we since we don't know what it changed from? + var ctVariationChangedToNothing = entity.IsPropertyDirty("Variations") && entity.Variations == ContentVariation.Nothing; + // insert or update properties // all of them, no-group and in-groups foreach (var propertyType in entity.PropertyTypes) { + //fixme: this does not take into account segments, but how can we since we don't know what it changed from? + if (ctVariationChangedToNothing && propertyType.Variations != ContentVariation.Nothing) + propertyType.Variations = ContentVariation.Nothing; + var groupId = propertyType.PropertyGroupId?.Value ?? default(int); // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias if (propertyType.DataTypeId == 0 || propertyType.DataTypeId == default(int)) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 8ece9621ce..ad16f4228a 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -24,6 +24,105 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase { + + //TODO: Then write the ones for content type changes + + [Test] + public void Change_Content_Type_From_Variant_Invariant() + { + //initialize the listener which is responsible for updating content based on variant/invariant changes + var listener = new UpdateContentOnVariantChangesComponent(); + listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); + + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + contentType.ResetDirtyProperties(false); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the content type to be invariant + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + } + + [Test] + public void Change_Property_Type_From_Invariant_Variant() + { + //initialize the listener which is responsible for updating content based on variant/invariant changes + var listener = new UpdateContentOnVariantChangesComponent(); + listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); + + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + contentType.ResetDirtyProperties(false); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change back property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + } + [Test] public void Change_Property_Type_From_Variant_Invariant() { From bc081db9e6af1cb41b777755a4b9b8b93b0fa963 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 4 Oct 2018 08:16:11 +0200 Subject: [PATCH 025/278] Sentences should end with a period (#3144) --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 8984ffa81a..71195be6f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -259,7 +259,7 @@ Slip filerne her... Link til medie eller klik her for at vælge filer - Du kan trække filer herind for at uploade + Du kan trække filer herind for at uploade. Tilladte filtyper er kun Kan ikke uploade denne fil, den har ikke en godkendt filtype Maks filstørrelse er diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 057eb7dc9f..f12d509416 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -267,7 +267,7 @@ Drop your files here... Link to media or click here to choose files - You can drag files here to upload + You can drag files here to upload. Only allowed file types are Cannot upload this file, it does not have an approved file type Max file size is @@ -647,7 +647,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we can not find what you are looking for. No items have been added Server Show 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 8a24c4a7c2..c6a6aadd77 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -268,7 +268,7 @@ Drop your files here... Link to media or click here to choose files - You can drag files here to upload + You can drag files here to upload. Only allowed file types are Cannot upload this file, it does not have an approved file type Max file size is @@ -647,7 +647,7 @@ Permissions Scheduled Publishing Search - Sorry, we can not find what you are looking for + Sorry, we can not find what you are looking for. No items have been added Server Show From deca5d7bdb056363175e6dd06e3415304a12d44b Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Wed, 3 Oct 2018 22:10:32 +0200 Subject: [PATCH 026/278] Refactor error handling to serve different error messages depending on the context. If we know that the id being moved is the same as the id we want to move it to then show a specific error message hinting the editor what's wrong and otherwise show the usual generic error message. --- .../src/common/resources/media.resource.js | 108 ++++++++++-------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 93a92db1ec..ca106cf65c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -46,7 +46,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * $scope.complete = true; * }); - * + * * @param {Object} args arguments object * @param {Int} args.parentId the ID of the parent node * @param {Array} options.sortedIds array of node IDs as they should be sorted @@ -87,9 +87,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -109,11 +109,23 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + + if(data.parentId === data.id){ + errorMsg = 'Media can\'t be moved into itself'; + } + + return { + errorMsg: errorMsg + }; + } + }); }, @@ -129,12 +141,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getById(1234)
           *    .then(function(media) {
-          *        var myMedia = media; 
+          *        var myMedia = media;
           *        alert('its here!');
           *    });
-          * 
- * - * @param {Int} id id of media item to return + * + * + * @param {Int} id id of media item to return * @returns {Promise} resourcePromise object containing the media item. * */ @@ -163,9 +175,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of media item to delete + * + * + * @param {Int} id id of media item to delete * @returns {Promise} resourcePromise object. * */ @@ -191,12 +203,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getByIds( [1234,2526,28262])
           *    .then(function(mediaArray) {
-          *        var myDoc = contentArray; 
+          *        var myDoc = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * - * @param {Array} ids ids of media items to return as an array + * + * + * @param {Array} ids ids of media items to return as an array * @returns {Promise} resourcePromise object containing the media items array. * */ @@ -223,28 +235,28 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * + * * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * * The scaffold is used to build editors for media that has not yet been populated with data. - * + * * ##usage *
           * mediaResource.getScaffold(1234, 'folder')
           *    .then(function(scaffold) {
           *        var myDoc = scaffold;
-          *        myDoc.name = "My new media item"; 
+          *        myDoc.name = "My new media item";
           *
           *        mediaResource.save(myDoc, true)
           *            .then(function(media){
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on + * @param {String} alias mediatype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the media scaffold. * */ @@ -283,11 +295,11 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + * + * * @param {Int} parentid id of content item to return children of * @param {Object} options optional options object * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 @@ -361,9 +373,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * mediaResource.getById(1234)
@@ -374,11 +386,11 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item * @returns {Promise} resourcePromise object containing the saved media item. * */ @@ -400,10 +412,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(folder) { * alert('New folder'); * }); - * + * * * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath + * @param {int} parentId Id of the media item to create the folder underneath * @returns {Promise} resourcePromise object. * */ @@ -427,18 +439,18 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * Retrieves all media children with types used as folders. * Uses the convention of looking for media items with mediaTypes ending in * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * + * * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * + * * ##usage *
           * mediaResource.getChildFolders(1234)
           *    .then(function(data) {
           *        alert('folders');
           *    });
-          * 
+ * * - * @param {int} parentId Id of the media item to query for child folders + * @param {int} parentId Id of the media item to query for child folders * @returns {Promise} resourcePromise object. * */ @@ -473,8 +485,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -501,8 +513,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(searchResult) { * alert('it's here!'); * }); - * - * + * + * * @param {string} query The search query * @param {int} pageNumber The page number * @param {int} pageSize The number of media items on a page @@ -513,7 +525,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { search: function (query, pageNumber, pageSize, searchFrom) { var args = [ - { "query": query }, + { "query": query }, { "pageNumber": pageNumber }, { "pageSize": pageSize }, { "searchFrom": searchFrom } From 7355ba4b235a2ec9ad3cd61262a56673e5cf9ac3 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 4 Oct 2018 08:36:21 +0200 Subject: [PATCH 027/278] Add option to rename colorpicker labels (#3121) --- .../src/less/property-editors.less | 19 +++++++++++-------- .../colorpicker/colorpicker.prevalues.html | 12 ++++++++---- .../multicolorpicker.controller.js | 6 ++++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 1c647eb1d0..d14a1abae9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -144,11 +144,14 @@ div.umb-codeeditor .umb-btn-toolbar { /* pre-value editor */ .control-group.color-picker-preval { .thumbnail { - width: 36px; + width: 34px; + height: 34px; min-width: auto; border: none; cursor: move; border-radius: 3px; + margin-top: auto; + margin-bottom: auto; } .handle { @@ -160,19 +163,19 @@ div.umb-codeeditor .umb-btn-toolbar { div.color-picker-prediv { display: inline-flex; align-items: center; - max-width: 85%; + max-width: 100%; + flex: 1; pre { display: inline-flex; font-family: monospace; - margin-right: 10px; - margin-left: 10px; + margin-left: 15px; + margin-right: 15px; white-space: nowrap; overflow: hidden; margin-bottom: 0; vertical-align: middle; - padding-top: 7px; - padding-bottom: 7px; + padding: 6px 10px; background: #f7f7f7; flex: 0 0 auto; } @@ -201,11 +204,11 @@ div.umb-codeeditor .umb-btn-toolbar { label { border: 1px solid #fff; - padding: 7px 10px; + padding: 6px 10px; font-family: monospace; border: 1px solid #dfdfe1; background: #f7f7f7; - margin: 0 15px 0 0; + margin: 0 15px 0 3px; border-radius: 3px; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 3d9e2efdf8..262c8593ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -3,18 +3,22 @@
- +
-
+
-
-
#{{item.value}}
{{item.label}}
+
+
+
#{{item.value}}
+ + +
Remove diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index 7ae728a85a..287a0f48fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -5,8 +5,9 @@ var defaultLabel = null; $scope.newColor = defaultColor; - $scope.newLavel = defaultLabel; + $scope.newLabel = defaultLabel; $scope.hasError = false; + $scope.focusOnNew = false; $scope.labels = {}; @@ -104,7 +105,6 @@ }; $scope.add = function (evt) { - evt.preventDefault(); if ($scope.newColor) { @@ -117,7 +117,9 @@ value: $scope.newColor, label: newLabel }); + $scope.newLabel = ""; $scope.hasError = false; + $scope.focusOnNew = true; return; } From ff7356fea29825d0bf93ba0d18ccfe1fdf36ad3b Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:06:07 +0200 Subject: [PATCH 028/278] Extending the AndSelect to take an param array of fields --- src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index d97c748b6f..46d44d6851 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -660,6 +660,18 @@ namespace Umbraco.Core.Persistence return sql.Select(sql.GetColumns(tableAlias: tableAlias, columnExpressions: fields)); } + /// + /// Adds columns to a SELECT Sql statement. + /// + /// The origin sql. + /// Expression indicating the column to select. + /// The Sql statement. + public static Sql AndSelect(this Sql sql, params string[] fields) + { + if (sql == null) throw new ArgumentNullException(nameof(sql)); + return sql.Append(", " + string.Join(", ", fields)); + } + /// /// Adds columns to a SELECT Sql statement. /// From f33953e702d4bad719f0bac880a3b7e6aa7797a9 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:12:49 +0200 Subject: [PATCH 029/278] Moving the sql join and coalesce query in the DocumentRepository.GetBaseQuery(), renaming "ordering" to "variantName" to match the field created in GetBaseQuery. --- .../Implement/ContentRepositoryBase.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 6ace73fbc3..974d3668fb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; +using Content = System.Web.UI.WebControls.Content; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -265,6 +266,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); + + + // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) + if (!ordering.Culture.IsNullOrWhiteSpace()) + { + for (var i = 0; i < psql.Arguments.Length; i++) + { + if (psql.Arguments[i] is string s && s == "[[[ISOCODE]]]") + { + psql.Arguments[i] = ordering.Culture; + break; + } + } + } return psql; } @@ -342,20 +357,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); - // culture = must work on variant name ?? invariant name - // insert proper join and return coalesced ordering field - - var joins = Sql() - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); - - // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", ", " + SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS ordering "), sql.Arguments); - - sql = InsertJoins(sql, joins); - - return "ordering"; + return "variantName"; } // previously, we'd accept anything and just sanitize it - not anymore @@ -435,6 +437,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // sort and filter sql = PreparePageSql(sql, filter, ordering); + // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); totalRecords = Convert.ToInt32(pagedResult.TotalItems); From 76bacb3ba08f180cbb398dac7a3a61ce11da6d8d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:15:01 +0200 Subject: [PATCH 030/278] Moved the join and coalesce into GetBaseQuery, making sure that we append the right query if we are searching a variant list --- .../Implement/DocumentRepository.cs | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index fb1a33eb62..1c3edaca40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using ColumnInfo = NPoco.ColumnInfo; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -110,12 +111,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(documentDto => documentDto.ContentDto, r1 => - r1.Select(contentDto => contentDto.NodeDto)) - .Select(documentDto => documentDto.DocumentVersionDto, r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) - .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => - r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))); + r.Select(documentDto => documentDto.ContentDto, r1 => + r1.Select(contentDto => contentDto.NodeDto)) + .Select(documentDto => documentDto.DocumentVersionDto, r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) + .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => + r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) + .AndSelect(SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName"); break; } @@ -125,17 +127,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On(left => left.NodeId, right => right.NodeId) // inner join on mandatory edited version - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.Id == right.Id) + .InnerJoin() + .On((left, right) => left.NodeId == right.NodeId) + .InnerJoin() + .On((left, right) => left.Id == right.Id) // left join on optional published version .LeftJoin(nested => - nested.InnerJoin("pdv").On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") + nested.InnerJoin("pdv") + .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + //the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code + sql + .LeftJoin(nested => + nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + sql .Where(x => x.NodeObjectType == NodeObjectTypeId); + // this would ensure we don't get the published version - keep for reference //sql // .WhereAny( @@ -146,6 +158,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (current) sql.Where(x => x.Current); // always get the current version + return sql; } @@ -235,7 +248,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -405,7 +418,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content) entity; + var content = (Content)entity; // check if we need to make any database changes at all if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) @@ -414,7 +427,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // whatever we do, we must check that we are saving the current version // fixme maybe we can just fetch Current (bool) var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault(); - if (version == null || !version.Current ) + if (version == null || !version.Current) throw new InvalidOperationException("Cannot save a non-current version."); // update @@ -683,16 +696,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// public override IEnumerable GetPage(IQuery query, - long pageIndex, int pageSize, out long totalRecords, + long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { Sql filterSql = null; + var temp = Query().Where(x => x.Name.Contains("foo")); + var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; + if (filter != null) { filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) - filterSql.Append($"AND ({filterClause.Item1})", filterClause.Item2); + { + // fixme - is this the right way of doing it??? + var where = filterClause.Item1.Split(' ')[0] == clause + // normally, this would be the field alias of the coalesce result between ContentVersionCulture and NodeDto names, however + // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field + ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + : filterClause.Item1; + + filterSql.Append( + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", + filterClause.Item2); + } } return GetPage(query, pageIndex, pageSize, out totalRecords, @@ -892,7 +919,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content) cached; + content[i] = (Content)cached; continue; } } @@ -1221,7 +1248,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // of whether the name has changed (ie the culture has been updated) - some saving culture // fr-FR could cause culture en-UK name to change - not sure that is clean - foreach(var (culture, name) in content.CultureNames) + foreach (var (culture, name) in content.CultureNames) { var langId = LanguageRepository.GetIdByIsoCode(culture); if (!langId.HasValue) continue; From 24fe3b33f2e65ab45aa8029a8eb0c98ecb3b8fc1 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 Oct 2018 13:20:15 +0200 Subject: [PATCH 031/278] Cleaning some usings --- .../Repositories/Implement/ContentRepositoryBase.cs | 4 ---- .../Persistence/Repositories/Implement/DocumentRepository.cs | 1 - 2 files changed, 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 974d3668fb..b87190ae58 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -10,7 +9,6 @@ using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; @@ -19,9 +17,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; -using Content = System.Web.UI.WebControls.Content; namespace Umbraco.Core.Persistence.Repositories.Implement { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 1c3edaca40..6fc3653db3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using ColumnInfo = NPoco.ColumnInfo; namespace Umbraco.Core.Persistence.Repositories.Implement { From 7b41730b6061227253e949a77da0720d9b8b4924 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Oct 2018 14:15:54 +0200 Subject: [PATCH 032/278] Changes how variant property data is updated with bulk SQL and gets tests working --- .../UpdateContentOnVariantChangesComponent.cs | 96 --------- src/Umbraco.Core/Models/ContentTypeBase.cs | 16 +- .../Persistence/Dtos/PropertyDataDto.cs | 2 +- .../Persistence/NPocoSqlExtensions.cs | 8 +- .../Implement/ContentTypeRepositoryBase.cs | 185 +++++++++++++++++- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../Services/ContentTypeServiceTests.cs | 23 ++- 7 files changed, 207 insertions(+), 124 deletions(-) delete mode 100644 src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs diff --git a/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs b/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs deleted file mode 100644 index 71fe4d46bd..0000000000 --- a/src/Umbraco.Core/Components/UpdateContentOnVariantChangesComponent.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Changes; -using Umbraco.Core.Services.Implement; - -namespace Umbraco.Core.Components -{ - /// - /// Manages data changes for when content/property types have variation changes - /// - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - //fixme: this one MUST fire before any of the content cache ones - public sealed class UpdateContentOnVariantChangesComponent : UmbracoComponentBase, IUmbracoCoreComponent - { - private IContentService _contentService; - private ILocalizationService _langService; - - public void Initialize(IContentService contentService, ILocalizationService langService) - { - _contentService = contentService; - _langService = langService; - ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; - } - - private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs e) - { - var defaultLang = _langService.GetDefaultLanguageIsoCode(); //this will be cached - - foreach (var c in e.Changes) - { - // existing property alias change? - var hasAnyPropertyVariationChanged = c.Item.WasPropertyTypeVariationChanged(out var aliases); - if (hasAnyPropertyVariationChanged) - { - var contentOfType = _contentService.GetByType(c.Item.Id); - - foreach (var a in aliases) - { - var propType = c.Item.PropertyTypes.First(x => x.Alias == a); - - //fixme: this does not take into account segments, but how can we since we don't know what it changed from? - - switch (propType.Variations) - { - case ContentVariation.Culture: - //if the current variation is culture it means that the previous was nothing - foreach (var content in contentOfType) - { - object invariantVal; - try - { - content.Properties[a].PropertyType.Variations = ContentVariation.Nothing; - //now get the invariant val - invariantVal = content.GetValue(a); - } - finally - { - content.Properties[a].PropertyType.Variations = ContentVariation.Culture; - } - //set the invariant value - content.SetValue(a, invariantVal, defaultLang); - } - break; - case ContentVariation.Nothing: - //if the current variation is nothing it means that the previous was culture - foreach(var content in contentOfType) - { - object cultureVal; - try - { - content.Properties[a].PropertyType.Variations = ContentVariation.Culture; - //now get the culture val - cultureVal = content.GetValue(a, defaultLang); - } - finally - { - content.Properties[a].PropertyType.Variations = ContentVariation.Nothing; - } - //set the invariant value - content.SetValue(a, cultureVal); - } - break; - default: - throw new NotSupportedException("The variation change is not supported"); - } - } - - _contentService.Save(contentOfType); - } - } - } - } -} diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 7467d83099..41d3419f98 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -409,7 +409,7 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] //fixme should we mark this as EditorBrowsable hidden since it really isn't ever used? - internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; + internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; /// /// Indicates whether a specific property on the current entity is dirty. @@ -418,12 +418,18 @@ namespace Umbraco.Core.Models /// True if Property is dirty, otherwise False public override bool IsPropertyDirty(string propertyName) { - bool existsInEntity = base.IsPropertyDirty(propertyName); + var existsInEntity = base.IsPropertyDirty(propertyName); + if (existsInEntity) return true; - bool anyDirtyGroups = PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); - bool anyDirtyTypes = PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); + //check properties types for this alias if it exists + if (PropertyTypeExists(propertyName)) + return PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); - return existsInEntity || anyDirtyGroups || anyDirtyTypes; + //check property groups for this alias if it exists + if (PropertyGroups.Contains(propertyName)) + return PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); + + return false; } /// diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs index cb47784d92..a3b28b5b54 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyDataDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos [ExplicitColumns] internal class PropertyDataDto { - private const string TableName = Constants.DatabaseSchema.Tables.PropertyData; + public const string TableName = Constants.DatabaseSchema.Tables.PropertyData; public const int VarcharLength = 512; public const int SegmentLength = 256; diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index d97c748b6f..4fdc72f52f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -1036,7 +1036,7 @@ namespace Umbraco.Core.Persistence { var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (TDto)); var tableName = tableAlias ?? pd.TableInfo.TableName; - var queryColumns = pd.QueryColumns; + var queryColumns = pd.QueryColumns.ToList(); Dictionary aliases = null; @@ -1056,7 +1056,11 @@ namespace Umbraco.Core.Persistence return fieldName; }).ToArray(); - queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToArray(); + //only get the columns that exist in the selected names + queryColumns = queryColumns.Where(x => names.Contains(x.Key)).ToList(); + + //ensure the order of the columns in the expressions is the order in the result + queryColumns.Sort((a, b) => names.IndexOf(a.Key).CompareTo(names.IndexOf(b.Key))); } string GetAlias(PocoColumn column) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 743b7c858e..13317e7b20 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -402,18 +402,44 @@ AND umbracoNode.id <> @id", propertyType.PropertyGroupId = new Lazy(() => groupId); } - //check if the content type variation has been changed to Nothing since we will need to update - //all property types to Nothing as well if they aren't already - //fixme: this does not take into account segments, but how can we since we don't know what it changed from? - var ctVariationChangedToNothing = entity.IsPropertyDirty("Variations") && entity.Variations == ContentVariation.Nothing; + //check if the content type variation has been changed + var ctVariationChanging = entity.IsPropertyDirty("Variations"); + if (ctVariationChanging) + { + //we've already looked up the previous version of the content type so we know it's previous variation state + MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations); + } + + //track any property types that are changing variation + var propertyTypeVariationChanges = new Dictionary(); // insert or update properties // all of them, no-group and in-groups foreach (var propertyType in entity.PropertyTypes) { - //fixme: this does not take into account segments, but how can we since we don't know what it changed from? - if (ctVariationChangedToNothing && propertyType.Variations != ContentVariation.Nothing) - propertyType.Variations = ContentVariation.Nothing; + //if the content type variation isn't changing track if any property type is changing + if (!ctVariationChanging) + { + if (propertyType.IsPropertyDirty("Variations")) + propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations; + } + else + { + switch(entity.Variations) + { + case ContentVariation.Nothing: + //if the content type is changing to Nothing, then all property type's must change to nothing + propertyType.Variations = ContentVariation.Nothing; + break; + case ContentVariation.Culture: + //we don't need to modify the property type in this case + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } var groupId = propertyType.PropertyGroupId?.Value ?? default(int); // if the Id of the DataType is not set, we resolve it from the db by its PropertyEditorAlias @@ -438,6 +464,27 @@ AND umbracoNode.id <> @id", orphanPropertyTypeIds.Remove(typeId); } + //check if any property types were changing variation + if (propertyTypeVariationChanges.Count > 0) + { + var changes = new Dictionary(); + + //now get the current property type variations for the changed ones so that we know which variation they + //are going from and to + var from = Database.Dictionary(Sql() + .Select(x => x.Id, x => x.Variations) + .From() + .WhereIn(x => x.Id, propertyTypeVariationChanges.Keys)); + + foreach(var f in from) + { + changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value); + } + + //perform the move + MoveVariantData(changes); + } + // deal with orphan properties: those that were in a deleted tab, // and have not been re-mapped to another tab or to 'generic properties' if (orphanPropertyTypeIds != null) @@ -445,6 +492,130 @@ AND umbracoNode.id <> @id", DeletePropertyType(entity.Id, id); } + /// + /// Moves variant data for property type changes + /// + /// + private void MoveVariantData(IDictionary propertyTypeChanges) + { + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefaultVariantLanguage)); + + //Group by the "To" variation so we can bulk update in the correct batches + foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2)) + { + var propertyTypeIds = g.Select(s => s.Key).ToList(); + + //the ContentVariation that the data is moving "To" + var toVariantType = g.Key; + + switch(toVariantType) + { + case ContentVariation.Culture: + + MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds); + + break; + case ContentVariation.Nothing: + + MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds); + + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } + } + + /// + /// Moves variant data for a content type variation change + /// + /// + /// + /// + private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to) + { + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefaultVariantLanguage)); + + var sqlPropertyTypeIds = Sql().Select(x => x.Id).From().Where(x => x.ContentTypeId == contentType.Id); + switch (to) + { + case ContentVariation.Culture: + + MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + + break; + case ContentVariation.Nothing: + + MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + + break; + case ContentVariation.CultureAndSegment: + case ContentVariation.Segment: + default: + throw new NotSupportedException(); //TODO: Support this + } + } + + private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) + { + //first clear out any existing property data that might already exists under the default lang + var sqlDelete = Sql() + .Delete() + .Where(x => x.LanguageId == null); + if (sqlPropertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + Database.Execute(sqlDelete); + + //now insert all property data into the default language that exists under the invariant lang + var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId); + var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue) + .Append(", NULL") //null language ID + .From() + .Where(x => x.LanguageId == defaultLangId); + if (sqlPropertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData); + + Database.Execute(sqlInsert); + } + + private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) + { + //first clear out any existing property data that might already exists under the default lang + var sqlDelete = Sql() + .Delete() + .Where(x => x.LanguageId == defaultLangId); + if (sqlPropertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlDelete.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + Database.Execute(sqlDelete); + + //now insert all property data into the default language that exists under the invariant lang + var cols = Sql().Columns(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue, x => x.LanguageId); + var sqlSelectData = Sql().Select(x => x.VersionId, x => x.PropertyTypeId, x => x.Segment, x => x.IntegerValue, x => x.DecimalValue, x => x.DateValue, x => x.VarcharValue, x => x.TextValue) + .Append($", {defaultLangId}") //default language ID + .From() + .Where(x => x.LanguageId == null); + if (sqlPropertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, sqlPropertyTypeIds); + if (propertyTypeIds != null) + sqlSelectData.WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + var sqlInsert = Sql($"INSERT INTO {PropertyDataDto.TableName} ({cols})").Append(sqlSelectData); + + Database.Execute(sqlInsert); + } + private void DeletePropertyType(int contentTypeId, int propertyTypeId) { // first clear dependencies diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 379d5ad48a..496a66137a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -170,7 +170,6 @@ - diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index ad16f4228a..5b4dcceb00 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -30,10 +30,6 @@ namespace Umbraco.Tests.Services [Test] public void Change_Content_Type_From_Variant_Invariant() { - //initialize the listener which is responsible for updating content based on variant/invariant changes - var listener = new UpdateContentOnVariantChangesComponent(); - listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; @@ -72,16 +68,23 @@ namespace Umbraco.Tests.Services ServiceContext.ContentTypeService.Save(contentType); doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + //at this stage all property types were switched to invariant so even though the variant value + //exists it will not be returned because the property type is invariant, + //so this check proves that null will be returned + Assert.IsNull(doc.GetValue("title", "en-US")); + + //we can now switch the property type to be variant and the value can be returned again + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + } [Test] public void Change_Property_Type_From_Invariant_Variant() { - //initialize the listener which is responsible for updating content based on variant/invariant changes - var listener = new UpdateContentOnVariantChangesComponent(); - listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; @@ -126,10 +129,6 @@ namespace Umbraco.Tests.Services [Test] public void Change_Property_Type_From_Variant_Invariant() { - //initialize the listener which is responsible for updating content based on variant/invariant changes - var listener = new UpdateContentOnVariantChangesComponent(); - listener.Initialize(ServiceContext.ContentService, ServiceContext.LocalizationService); - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; From 9d36ee6bd8a87e49704b23995c34e81384a7414e Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Thu, 4 Oct 2018 15:49:32 +0200 Subject: [PATCH 033/278] #3141 fixed broken Do something else button --- .../src/common/services/navigation.service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index da2fd243e6..8fb7137e9a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -73,7 +73,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("showSearchResults", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); - appState.setMenuState("currentNode", null); if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); From c5ae149d4054268d58971a80debfaf45ba878652 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Oct 2018 16:27:41 +0200 Subject: [PATCH 034/278] Gets remaining tests in place along with moving name values --- .../Implement/ContentTypeRepositoryBase.cs | 91 ++++++++++++++++++- .../Implement/LanguageRepository.cs | 5 + .../Services/ContentTypeServiceTests.cs | 72 +++++++++++++-- 3 files changed, 154 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 13317e7b20..eed7b5a071 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -511,14 +511,10 @@ AND umbracoNode.id <> @id", switch(toVariantType) { case ContentVariation.Culture: - MovePropertyDataToVariantCulture(defaultLangId, propertyTypeIds: propertyTypeIds); - break; case ContentVariation.Nothing: - MovePropertyDataToVariantNothing(defaultLangId, propertyTypeIds: propertyTypeIds); - break; case ContentVariation.CultureAndSegment: case ContentVariation.Segment: @@ -542,14 +538,89 @@ AND umbracoNode.id <> @id", switch (to) { case ContentVariation.Culture: + //move the property data MovePropertyDataToVariantCulture(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + //now we need to move the names + //first clear out any existing names that might already exists under the default lang + //there's 2x tables to update + + //clear out the versionCultureVariation table + var sqlSelect = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.Id, x => x.VersionId) + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id) + .Where(x => x.LanguageId == defaultLangId); + var sqlDelete = Sql() + .Delete() + .WhereIn(x => x.Id, sqlSelect); + Database.Execute(sqlDelete); + + //clear out the documentCultureVariation table + sqlSelect = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id) + .Where(x => x.LanguageId == defaultLangId); + sqlDelete = Sql() + .Delete() + .WhereIn(x => x.Id, sqlSelect); + Database.Execute(sqlDelete); + + //now we need to insert names into these 2 tables based on the invariant data + + //insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang + var cols = Sql().Columns(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId); + sqlSelect = Sql().Select(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate) + .Append($", {defaultLangId}") //default language ID + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect); + Database.Execute(sqlInsert); + + //insert rows into the documentCultureVariation table + cols = Sql().Columns(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId); + sqlSelect = Sql().Select(x => x.NodeId, x => x.Edited, x => x.Published) + .AndSelect(x => x.Text) + .Append($", 1, {defaultLangId}") //make Available + default language ID + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect); + Database.Execute(sqlInsert); + break; case ContentVariation.Nothing: + //move the property data MovePropertyDataToVariantNothing(defaultLangId, sqlPropertyTypeIds: sqlPropertyTypeIds); + //we dont need to move the names! this is because we always keep the invariant names with the name of the default language. + + //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :( + + ////now we need to move the names + + ////first update umbracoNode from documentCultureVariation + //var sqlUpdate = Sql($"UPDATE {NodeDto.TableName} SET {Sql().Columns(x => x.Text)} = {Sql().Columns(x => x.Name)}") + // .From() + // .InnerJoin().On(x => x.NodeId, x => x.NodeId) + // .InnerJoin().On(x => x.NodeId, x => x.NodeId) + // .Where(x => x.ContentTypeId == contentType.Id); + //Database.Execute(sqlUpdate); + + ////next update umbracoContentVersion from contentVersionCultureVariation + //sqlUpdate = Sql($"UPDATE {ContentVersionDto.TableName} SET {Sql().Columns(x => x.Text)} = {Sql().Columns(x => x.Name)}") + // .From() + // .InnerJoin().On(x => x.VersionId, x => x.Id) + // .InnerJoin().On(x => x.NodeId, x => x.NodeId) + // .Where(x => x.ContentTypeId == contentType.Id); + //Database.Execute(sqlUpdate); + break; case ContentVariation.CultureAndSegment: case ContentVariation.Segment: @@ -558,6 +629,12 @@ AND umbracoNode.id <> @id", } } + /// + /// This will move all property data from variant to invariant + /// + /// + /// Optional list of property type ids of the properties to be updated + /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated private void MovePropertyDataToVariantNothing(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) { //first clear out any existing property data that might already exists under the default lang @@ -587,6 +664,12 @@ AND umbracoNode.id <> @id", Database.Execute(sqlInsert); } + /// + /// This will move all property data from invariant to variant + /// + /// + /// Optional list of property type ids of the properties to be updated + /// Optional SQL statement used for the sub-query to select the properties type ids for the properties to be updated private void MovePropertyDataToVariantCulture(int defaultLangId, IReadOnlyCollection propertyTypeIds = null, Sql sqlPropertyTypeIds = null) { //first clear out any existing property data that might already exists under the default lang diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 2026daba74..8a0ba73f85 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -219,6 +219,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (TypedCachePolicy != null) TypedCachePolicy.GetAllCached(PerformGetAll); + var id = GetIdByIsoCode(isoCode, throwOnNotFound: false); return id.HasValue ? Get(id.Value) : null; } @@ -234,6 +235,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // ensure cache is populated, in a non-expensive way if (TypedCachePolicy != null) TypedCachePolicy.GetAllCached(PerformGetAll); + else + PerformGetAll(); //we don't have a typed cache (i.e. unit tests) but need to populate the _codeIdMap lock (_codeIdMap) { @@ -255,6 +258,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // ensure cache is populated, in a non-expensive way if (TypedCachePolicy != null) TypedCachePolicy.GetAllCached(PerformGetAll); + else + PerformGetAll(); lock (_codeIdMap) // yes, we want to lock _codeIdMap { diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 5b4dcceb00..892166e566 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -25,7 +25,55 @@ namespace Umbraco.Tests.Services public class ContentTypeServiceTests : TestWithSomeContentBase { - //TODO: Then write the ones for content type changes + [Test] + public void Change_Content_Type_From_Invariant_Variant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("Hello1", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the content type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello2"; + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello2", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant + + //change back property type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello3", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello3", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + } [Test] public void Change_Content_Type_From_Variant_Invariant() @@ -45,25 +93,30 @@ namespace Umbraco.Tests.Services Variations = ContentVariation.Culture }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - contentType.ResetDirtyProperties(false); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Home", "en-US"); + doc.SetCultureName("Hello1", "en-US"); doc.SetValue("title", "hello world", "en-US"); ServiceContext.ContentService.Save(doc); + Assert.AreEqual("Hello1", doc.GetCultureName("en-US")); Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - //change the content type to be invariant - contentType.Variations = ContentVariation.Nothing; + //change the content type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello2", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(contentType); doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + Assert.AreEqual("Hello2", doc.Name); Assert.AreEqual("hello world", doc.GetValue("title")); - //change back property type to be variant + //change back property type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello3"; + ServiceContext.ContentService.Save(doc); contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); doc = ServiceContext.ContentService.GetById(doc.Id); //re-get @@ -78,6 +131,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentTypeService.Save(contentType); doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + Assert.AreEqual("Hello3", doc.GetCultureName("en-US")); Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); } @@ -87,7 +141,7 @@ namespace Umbraco.Tests.Services { //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; + contentType.Variations = ContentVariation.Nothing; var contentCollection = new PropertyTypeCollection(true); contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { @@ -100,12 +154,11 @@ namespace Umbraco.Tests.Services Variations = ContentVariation.Nothing }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - contentType.ResetDirtyProperties(false); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Home", "en-US"); + doc.Name = "Home"; doc.SetValue("title", "hello world"); ServiceContext.ContentService.Save(doc); @@ -144,7 +197,6 @@ namespace Umbraco.Tests.Services Variations = ContentVariation.Culture }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - contentType.ResetDirtyProperties(false); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type From 304c874006f35634101b81e024ee08372237411a Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Oct 2018 16:36:19 +0200 Subject: [PATCH 035/278] removes comments --- .../Implement/ContentTypeRepositoryBase.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index eed7b5a071..93f8b123f7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -602,24 +602,7 @@ AND umbracoNode.id <> @id", //we dont need to move the names! this is because we always keep the invariant names with the name of the default language. //however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :( - - ////now we need to move the names - - ////first update umbracoNode from documentCultureVariation - //var sqlUpdate = Sql($"UPDATE {NodeDto.TableName} SET {Sql().Columns(x => x.Text)} = {Sql().Columns(x => x.Name)}") - // .From() - // .InnerJoin().On(x => x.NodeId, x => x.NodeId) - // .InnerJoin().On(x => x.NodeId, x => x.NodeId) - // .Where(x => x.ContentTypeId == contentType.Id); - //Database.Execute(sqlUpdate); - - ////next update umbracoContentVersion from contentVersionCultureVariation - //sqlUpdate = Sql($"UPDATE {ContentVersionDto.TableName} SET {Sql().Columns(x => x.Text)} = {Sql().Columns(x => x.Name)}") - // .From() - // .InnerJoin().On(x => x.VersionId, x => x.Id) - // .InnerJoin().On(x => x.NodeId, x => x.NodeId) - // .Where(x => x.ContentTypeId == contentType.Id); - //Database.Execute(sqlUpdate); + // if we want these SQL statements back, look into GIT history break; case ContentVariation.CultureAndSegment: From 0ae1d06c2d6212a9dde15b482bc3bd8f5a8b18d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 4 Oct 2018 16:39:31 +0200 Subject: [PATCH 036/278] fix merge --- .../Repositories/Implement/ContentTypeRepositoryBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 93f8b123f7..19f3623680 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -498,7 +498,7 @@ AND umbracoNode.id <> @id", /// private void MoveVariantData(IDictionary propertyTypeChanges) { - var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefaultVariantLanguage)); + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault)); //Group by the "To" variation so we can bulk update in the correct batches foreach(var g in propertyTypeChanges.GroupBy(x => x.Value.Item2)) @@ -532,7 +532,7 @@ AND umbracoNode.id <> @id", /// private void MoveVariantData(IContentTypeComposition contentType, ContentVariation from, ContentVariation to) { - var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefaultVariantLanguage)); + var defaultLangId = Database.First(Sql().Select(x => x.Id).From().Where(x => x.IsDefault)); var sqlPropertyTypeIds = Sql().Select(x => x.Id).From().Where(x => x.ContentTypeId == contentType.Id); switch (to) From b6c32978848e7c3ef3f43d1689ea03a390e19c96 Mon Sep 17 00:00:00 2001 From: Jannik Anker Date: Thu, 4 Oct 2018 17:16:57 +0200 Subject: [PATCH 037/278] Lecoati CSS reference removed (issue #3147) ApprovedColorPickerController had a reference for no apparent reason. --- .../src/views/common/dialogs/approvedcolorpicker.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js index 5e5363ae6c..7d9a0d3410 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/approvedcolorpicker.controller.js @@ -19,7 +19,6 @@ angular.module("umbraco") $scope.classes.splice(0, 0, "noclass"); }) - assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css", $scope); assetsService.loadCss(cssPath, $scope); }); }); From e7d7de627307f9e0d3b5e00908c017f42057cee4 Mon Sep 17 00:00:00 2001 From: Rahul Arulkumaran Date: Fri, 5 Oct 2018 11:43:41 +0530 Subject: [PATCH 038/278] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index c5560c3ce1..fa83dba963 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) # -Copyright (c) 2013 Umbraco +Copyright (c) 2013-present Umbraco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 2e5d0f85e81cfd3c7b07a4d53b1828e196f97589 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Oct 2018 11:53:35 +0200 Subject: [PATCH 039/278] clears redirects on variation change --- .../Implement/ContentTypeRepositoryBase.cs | 21 +++++++++ .../Services/ContentTypeServiceTests.cs | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 19f3623680..7104c9f713 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -274,6 +274,8 @@ AND umbracoNode.id <> @id", if (compositionBase != null && compositionBase.RemovedContentTypeKeyTracker != null && compositionBase.RemovedContentTypeKeyTracker.Any()) { + //TODO: Could we do the below with bulk SQL statements instead of looking everything up and then manipulating? + // find Content based on the current ContentType var sql = Sql() .SelectAll() @@ -292,6 +294,7 @@ AND umbracoNode.id <> @id", // based on the PropertyTypes that belong to the removed ContentType. foreach (var contentDto in contentDtos) { + //TODO: This could be done with bulk SQL statements foreach (var propertyType in propertyTypes) { var nodeId = contentDto.NodeId; @@ -408,6 +411,7 @@ AND umbracoNode.id <> @id", { //we've already looked up the previous version of the content type so we know it's previous variation state MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations); + Clear301Redirects(entity); } //track any property types that are changing variation @@ -492,6 +496,23 @@ AND umbracoNode.id <> @id", DeletePropertyType(entity.Id, id); } + /// + /// Clear any redirects associated with content for a content type + /// + private void Clear301Redirects(IContentTypeComposition contentType) + { + //first clear out any existing property data that might already exists under the default lang + var sqlSelect = Sql().Select(x => x.UniqueId) + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .Where(x => x.ContentTypeId == contentType.Id); + var sqlDelete = Sql() + .Delete() + .WhereIn((System.Linq.Expressions.Expression>)(x => x.ContentKey), sqlSelect); + + Database.Execute(sqlDelete); + } + /// /// Moves variant data for property type changes /// diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 892166e566..2d0471b9bd 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -24,6 +24,50 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class ContentTypeServiceTests : TestWithSomeContentBase { + [Test] + public void Change_Content_Type_Variation_Clears_Redirects() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + ServiceContext.ContentService.Save(doc2); + + ServiceContext.RedirectUrlService.Register("hello/world", doc.Key); + ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key); + + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + //change variation + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + } [Test] public void Change_Content_Type_From_Invariant_Variant() From 06d41086f310a009d5cf6485b2212cf433b2a07e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Oct 2018 11:56:47 +0200 Subject: [PATCH 040/278] adds stub code for clearing scheduled publishing --- .../Repositories/Implement/ContentTypeRepositoryBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 7104c9f713..d1737d495a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -412,6 +412,7 @@ AND umbracoNode.id <> @id", //we've already looked up the previous version of the content type so we know it's previous variation state MoveVariantData(entity, (ContentVariation)dtoPk.Variations, entity.Variations); Clear301Redirects(entity); + ClearScheduledPublishing(entity); } //track any property types that are changing variation @@ -513,6 +514,14 @@ AND umbracoNode.id <> @id", Database.Execute(sqlDelete); } + /// + /// Clear any scheduled publishing associated with content for a content type + /// + private void ClearScheduledPublishing(IContentTypeComposition contentType) + { + //TODO: Fill this in when scheduled publishing is enabled for variants + } + /// /// Moves variant data for property type changes /// From 4ff15fbf047fe1ad43329de53c2fba9489a1483e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:09:30 +0100 Subject: [PATCH 041/278] Updates constants to include nested class in Constants.Trees.Groups for the various settings section subtree groupings - these keys will be used to help group the trees --- src/Umbraco.Core/Constants-Applications.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 9a1883a065..1bb7585aef 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -145,6 +145,15 @@ public const string PartialViewMacros = "partialViewMacros"; + public static class Groups + { + public const string Settings = "settingsGroup"; + + public const string Templating = "templatingGroup"; + + public const string ThirdParty = "thirdPartyGroup"; + } + //TODO: Fill in the rest! } } From e013545cfc324b6733cd0be8e34ec3eb3eda5ffa Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:10:31 +0100 Subject: [PATCH 042/278] Adds property to [CoreTree] attribute that allows you to set the TreeGroup key - which will be from our constants --- src/Umbraco.Web/Trees/CoreTreeAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs index 160f2a36d8..1b485aea6a 100644 --- a/src/Umbraco.Web/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Web/Trees/CoreTreeAttribute.cs @@ -11,6 +11,11 @@ namespace Umbraco.Web.Trees [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] internal class CoreTreeAttribute : Attribute { + public string TreeGroup { get; set; } + public CoreTreeAttribute() + { + + } } } From 2612f3328333a0b4e6d45500e458bc0a3e7fa8a0 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 5 Oct 2018 13:11:38 +0100 Subject: [PATCH 043/278] Update trees using [CoreTree] attribute to use new property from our constants --- src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs | 2 +- src/Umbraco.Web/Trees/ContentTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/DataTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/DictionaryTreeController.cs | 2 +- src/Umbraco.Web/Trees/LanguageTreeController.cs | 2 +- src/Umbraco.Web/Trees/MediaTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/PartialViewsTreeController.cs | 2 +- src/Umbraco.Web/Trees/TemplatesTreeController.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 145a0f5947..51b94098ba 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoApplicationAuthorize(Constants.Applications.Content)] [Tree(Constants.Applications.Settings, Constants.Trees.ContentBlueprints, null, sortOrder: 10)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentBlueprintTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index da1eae7b2c..0f395b10fc 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DocumentTypes, null, sortOrder: 0)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class ContentTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 183889777b..231a06ce4e 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.DataTypes, null, sortOrder:7)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class DataTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs index c2491fefe0..f3288245e5 100644 --- a/src/Umbraco.Web/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, null, sortOrder: 3)] public class DictionaryTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/LanguageTreeController.cs b/src/Umbraco.Web/Trees/LanguageTreeController.cs index eadb5c50d0..304eb91386 100644 --- a/src/Umbraco.Web/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web/Trees/LanguageTreeController.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Languages)] [Tree(Constants.Applications.Settings, Constants.Trees.Languages, null, sortOrder: 5)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class LanguageTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 086c1a5194..074f480fce 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.MediaTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MediaTypes, null, sortOrder:9)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class MediaTypeTreeController : TreeController, ISearchableTree { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index aaaeb2d175..4011c4efed 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, null, sortOrder: 2)] [UmbracoTreeAuthorize(Constants.Trees.PartialViews)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewsTreeController : FileSystemTreeController { protected override IFileSystem FileSystem => Current.FileSystems.PartialViewsFileSystem; diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index d9aa0f21a0..1ffcdcbb99 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Templates)] [Tree(Constants.Applications.Settings, Constants.Trees.Templates, null, sortOrder:1)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class TemplatesTreeController : TreeController, ISearchableTree { /// From fdc94b9d1b423b54982e33fb576e17dd5b7c5bfe Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Oct 2018 15:07:19 +0200 Subject: [PATCH 044/278] Writes tests to ensure that property values are updated in docs that are of a composed type of the one that variants are being changed in. --- .../Models/ContentTypeBaseExtensions.cs | 15 +++ .../Models/IContentTypeComposition.cs | 1 + .../IContentTypeRepositoryBase.cs | 6 + .../Implement/ContentTypeRepositoryBase.cs | 10 +- .../Services/ContentTypeServiceExtensions.cs | 18 +-- ...peServiceBaseOfTRepositoryTItemTService.cs | 3 + .../Services/ContentTypeServiceTests.cs | 113 ++++++++++++++++++ .../Editors/ContentTypeControllerBase.cs | 2 +- 8 files changed, 148 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index 8af48bb881..0d2f817660 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -63,5 +63,20 @@ namespace Umbraco.Core.Models aliases = a; return hasAnyPropertyVariationChanged; } + + /// + /// Returns the list of content types the composition is used in + /// + /// + /// + /// + internal static IEnumerable GetWhereCompositionIsUsedInContentTypes(this IContentTypeComposition source, + IContentTypeComposition[] allContentTypes) + { + var sourceId = source != null ? source.Id : 0; + + // find which content types are using this composition + return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray(); + } } } diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index b5277a23be..36ace19f0f 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -10,6 +10,7 @@ namespace Umbraco.Core.Models /// /// Gets or sets the content types that compose this content type. /// + //fixme: we should be storing key references, not the object else we are caching way too much IEnumerable ContentTypeComposition { get; set; } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index f5e9013082..3bb1ac38ca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -10,6 +10,12 @@ namespace Umbraco.Core.Persistence.Repositories { TItem Get(string alias); IEnumerable> Move(TItem moving, EntityContainer container); + + /// + /// Returns the content types that are direct compositions of the content type + /// + /// The content type id + /// IEnumerable GetTypesDirectlyComposedOf(int id); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index d1737d495a..3f1ea3116e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -415,7 +415,7 @@ AND umbracoNode.id <> @id", ClearScheduledPublishing(entity); } - //track any property types that are changing variation + //track any content type/property types that are changing variation which will require content updates var propertyTypeVariationChanges = new Dictionary(); // insert or update properties @@ -426,7 +426,9 @@ AND umbracoNode.id <> @id", if (!ctVariationChanging) { if (propertyType.IsPropertyDirty("Variations")) + { propertyTypeVariationChanges[propertyType.Id] = propertyType.Variations; + } } else { @@ -481,7 +483,7 @@ AND umbracoNode.id <> @id", .From() .WhereIn(x => x.Id, propertyTypeVariationChanges.Keys)); - foreach(var f in from) + foreach (var f in from) { changes[f.Key] = (propertyTypeVariationChanges[f.Key], (ContentVariation)f.Value); } @@ -490,6 +492,7 @@ AND umbracoNode.id <> @id", MoveVariantData(changes); } + // deal with orphan properties: those that were in a deleted tab, // and have not been re-mapped to another tab or to 'generic properties' if (orphanPropertyTypeIds != null) @@ -845,8 +848,11 @@ AND umbracoNode.id <> @id", } } + /// public IEnumerable GetTypesDirectlyComposedOf(int id) { + //fixme - this will probably be more efficient to simply load all content types and do the calculation, see GetWhereCompositionIsUsedInContentTypes + var sql = Sql() .SelectAll() .From() diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs index 9e82213aa5..66b3982b49 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -109,23 +109,7 @@ namespace Umbraco.Core.Services return new ContentTypeAvailableCompositionsResults(ancestors, result); } - /// - /// Returns the list of content types the composition is used in - /// - /// - /// - /// - /// - internal static IEnumerable GetWhereCompositionIsUsedInContentTypes(this IContentTypeService ctService, - IContentTypeComposition source, - IContentTypeComposition[] allContentTypes) - { - - var sourceId = source != null ? source.Id : 0; - - // find which content types are using this composition - return allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray(); - } + private static IContentTypeComposition[] GetAncestors(IContentTypeComposition ctype, IContentTypeComposition[] allContentTypes) { diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index a3bc87315b..a114f415cc 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -346,6 +346,9 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetComposedOf(int id) { + //fixme: this is essentially the same as ContentTypeServiceExtensions.GetWhereCompositionIsUsedInContentTypes which loads + // all content types to figure this out, this instead makes quite a few queries so should be replaced + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(ReadLockIds); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index 2d0471b9bd..f25382d557 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -266,6 +266,119 @@ namespace Umbraco.Tests.Services Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); } + [Test] + public void Change_Property_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.AreEqual("hello world", doc2.GetValue("title", "en-US")); + } + + [Test] + public void Change_Content_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the content type to be invariant + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back content type to be variant + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + //this will be null because the doc type was changed back to variant but it's property types don't get changed back + Assert.IsNull(doc.GetValue("title", "en-US")); + Assert.IsNull(doc2.GetValue("title", "en-US")); + } + [Test] public void Deleting_Media_Type_With_Hierarchy_Of_Media_Items_Moves_Orphaned_Media_To_Recycle_Bin() { diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index fa1aaf7345..22d86631ca 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors throw new ArgumentOutOfRangeException("The entity type was not a content type"); } - var contentTypesWhereCompositionIsUsed = Services.ContentTypeService.GetWhereCompositionIsUsedInContentTypes(source, allContentTypes); + var contentTypesWhereCompositionIsUsed = source.GetWhereCompositionIsUsedInContentTypes(allContentTypes); return contentTypesWhereCompositionIsUsed .Select(x => Mapper.Map(x)) .Select(x => From 774f1d706c886f071df2f6c1e284911e2ad38e7c Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Thu, 4 Oct 2018 16:21:11 +0100 Subject: [PATCH 045/278] Fixed issue with actions dropdown menu being partially visible. --- src/Umbraco.Web.UI.Client/src/less/panel.less | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 51a2504363..4e26ab73b6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -368,7 +368,6 @@ flex-direction: column; height: 99px; padding: 0 20px; - overflow-y: hidden; } .umb-panel-header-content { From 0f3da1ae1446c9e1317b2e8888618a1f54710fec Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Mon, 8 Oct 2018 08:38:24 +0200 Subject: [PATCH 046/278] #3159 - Add border around the colors from the approved color picker (#3161) --- .../src/less/components/umb-color-swatches.less | 5 +++-- .../src/views/propertyeditors/colorpicker/colorpicker.html | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index df80aef2ce..d8e67444a1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -3,7 +3,7 @@ flex-flow: row wrap; .umb-color-box { - border: none; + border: 1px solid @gray-8; color: white; cursor: pointer; padding: 1px; @@ -69,7 +69,8 @@ margin-right: -1px; text-indent: 0; text-align: left; - border: 1px solid @gray-8; + border-top: 1px solid @gray-8; + border-bottom: 1px solid @gray-8; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; overflow: hidden; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html index ccea7519ac..b09d73cee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html @@ -1,5 +1,6 @@ 
+
You haven't defined any colors
From 13693bddef38adee0652b3b6a52925c103e69d6d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Oct 2018 10:54:02 +0200 Subject: [PATCH 047/278] Ensures the correct default list view settings are loaded for media content type --- .../ContentApps/ListViewContentAppDefinition.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 5c73b2fa8c..2b5e1a0910 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -58,8 +58,21 @@ namespace Umbraco.Web.ContentApps Weight = Weight }; + int dtdId; + switch(entityType) + { + case "content": + dtdId = Core.Constants.DataTypes.DefaultContentListView; + break; + case "media": + dtdId = Core.Constants.DataTypes.DefaultMediaListView; + break; + default: + throw new NotSupportedException($"Entity type {entityType} is not supported here."); + } + var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - var dtdId = Core.Constants.DataTypes.DefaultContentListView; + //first try to get the custom one if there is one var dt = dataTypeService.GetDataType(customDtdName) ?? dataTypeService.GetDataType(dtdId); From 975e08ba9f0abe1b57a1cfe4c651c546ddbe0c62 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Oct 2018 11:23:12 +0200 Subject: [PATCH 048/278] Fixes member/media list views, reformats some code and fixes js error --- .../views/member/member.list.controller.js | 2 +- .../listview/layouts/grid/grid.html | 136 ++++++++---------- .../ListViewContentAppDefinition.cs | 31 ++-- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/MediaController.cs | 4 +- src/Umbraco.Web/Editors/MemberController.cs | 2 +- 6 files changed, 82 insertions(+), 95 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js index 9fc03349e8..b099396142 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.list.controller.js @@ -39,7 +39,7 @@ function MemberListController($scope, $routeParams, $location, $q, $window, appS // route but there might be server validation errors in the collection which we need to display // after the redirect, so we will bind all subscriptions which will show the server validation errors // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); + serverValidationManager.notifyAndClearAllSubscriptions(); $scope.page.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index 29dd1b870f..6c7a9c7f06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -1,91 +1,79 @@
-
+
- - + + - -
No content has been added
-
No members have been added
-
+ +
No content has been added
+
No members have been added
+
- - - + + + - - - + + + -
+
-
+
- - + + - - + + - - + + - -
    -
  • -
    {{ property.header }}
    -
    {{ vm.mediaDetailsTooltip.item[property.alias] }}
    -
  • -
-
+ +
    +
  • +
    {{ property.header }}
    +
    {{ vm.mediaDetailsTooltip.item[property.alias] }}
    +
  • +
+
- - - + + + - - - + + +
diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 2b5e1a0910..c5636d3a02 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -25,6 +25,7 @@ namespace Umbraco.Web.ContentApps public ContentApp GetContentAppFor(object o) { string contentTypeAlias, entityType; + int dtdId; switch (o) { @@ -33,22 +34,33 @@ namespace Umbraco.Web.ContentApps case IContent content: contentTypeAlias = content.ContentType.Alias; entityType = "content"; + dtdId = Core.Constants.DataTypes.DefaultContentListView; break; case IMedia media when !media.ContentType.IsContainer && media.ContentType.Alias != Core.Constants.Conventions.MediaTypes.Folder: return null; case IMedia media: contentTypeAlias = media.ContentType.Alias; entityType = "media"; + dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias); + return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); } - public static ContentApp CreateContentApp(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors, string entityType, string contentTypeAlias) + public static ContentApp CreateContentApp(IDataTypeService dataTypeService, + PropertyEditorCollection propertyEditors, + string entityType, string contentTypeAlias, + int defaultListViewDataType) { + if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); + if (propertyEditors == null) throw new ArgumentNullException(nameof(propertyEditors)); + if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentException("message", nameof(entityType)); + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("message", nameof(contentTypeAlias)); + if (defaultListViewDataType == default) throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); + var contentApp = new ContentApp { Alias = "umbListView", @@ -58,24 +70,11 @@ namespace Umbraco.Web.ContentApps Weight = Weight }; - int dtdId; - switch(entityType) - { - case "content": - dtdId = Core.Constants.DataTypes.DefaultContentListView; - break; - case "media": - dtdId = Core.Constants.DataTypes.DefaultMediaListView; - break; - default: - throw new NotSupportedException($"Entity type {entityType} is not supported here."); - } - var customDtdName = Core.Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; //first try to get the custom one if there is one var dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(dtdId); + ?? dataTypeService.GetDataType(defaultListViewDataType); if (dt == null) { diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1fcee6d727..adc2a78804 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -232,7 +232,7 @@ namespace Umbraco.Web.Editors public ContentItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "content", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new ContentItemDisplay { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 635cdfaa17..dc744ea361 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -99,7 +99,7 @@ namespace Umbraco.Web.Editors public MediaItemDisplay GetRecycleBin() { var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, "recycleBin", "media", Core.Constants.DataTypes.DefaultMediaListView)); apps[0].Active = true; var display = new MediaItemDisplay { @@ -913,4 +913,4 @@ namespace Umbraco.Web.Editors return hasPathAccess; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 9f70c3c33b..6117db8857 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -139,7 +139,7 @@ namespace Umbraco.Web.Editors var name = foundType != null ? foundType.Name : listName; var apps = new List(); - apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member")); + apps.Add(ListViewContentAppDefinition.CreateContentApp(Services.DataTypeService, _propertyEditors, listName, "member", Core.Constants.DataTypes.DefaultMembersListView)); apps[0].Active = true; var display = new MemberListDisplay From a9756e065cb27d06a72124b1ed1c9f98b1423b70 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 8 Oct 2018 13:07:30 +0200 Subject: [PATCH 049/278] Don't show toggles to create templates when that feature is disabled. Also default the toggle to false when the feature is disabled. --- .../src/views/documenttypes/create.controller.js | 4 ++-- .../src/views/documenttypes/create.html | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 551a5d6ec1..b05cb52fe9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -27,8 +27,8 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.showCreateDocTypeCollection = function () { $scope.model.creatingDoctypeCollection = true; - $scope.model.collectionCreateTemplate = true; - $scope.model.collectionItemCreateTemplate = true; + $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates; + $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates; }; $scope.createContainer = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 98f9b23b64..3519195848 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -19,8 +19,8 @@ - - Document type> + + Document type> @@ -80,14 +80,18 @@ - - + + + + - - + + + + From 4d7a36e017772a3d9fd76a69ec7a3e5db24319d6 Mon Sep 17 00:00:00 2001 From: KimHolzmann Date: Mon, 8 Oct 2018 14:04:52 +0200 Subject: [PATCH 050/278] #3001 Styles cannot be saved - Not sure if oldName is legacy (#3123) --- .../WebServices/SaveFileController.cs | 50 +++++++++++-------- .../stylesheet/editstylesheet.aspx.cs | 4 +- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index fe93fb1e06..5f2fcaeb34 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -39,13 +39,13 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialViewMacro(string filename, string oldName, string contents) { - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; return SavePartialView(svce, filename, oldName, contents, "MacroPartials/", (s, n) => s.GetPartialViewMacro(n), - (s, v) => s.ValidatePartialViewMacro((PartialView) v), + (s, v) => s.ValidatePartialViewMacro((PartialView)v), (s, v) => s.SavePartialViewMacro(v)); } @@ -59,13 +59,13 @@ namespace Umbraco.Web.WebServices [HttpPost] public JsonResult SavePartialView(string filename, string oldName, string contents) { - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; return SavePartialView(svce, filename, oldName, contents, "Partials/", (s, n) => s.GetPartialView(n), - (s, v) => s.ValidatePartialView((PartialView) v), + (s, v) => s.ValidatePartialView((PartialView)v), (s, v) => s.SavePartialView(v)); } @@ -77,9 +77,7 @@ namespace Umbraco.Web.WebServices Func> save) { // sanitize input - partial view names have an extension - filename = filename - .Replace('\\', '/') - .TrimStart('/'); + filename = CleanFilename(filename); // sharing the editor with partial views & partial view macros, // using path prefix to differenciate, @@ -98,7 +96,7 @@ namespace Umbraco.Web.WebServices oldname = oldname.TrimStart(pathPrefix); } - var currentView = oldname.IsNullOrWhiteSpace() + var currentView = oldname.IsNullOrWhiteSpace() ? get(svce, filename) : get(svce, oldname); @@ -108,7 +106,7 @@ namespace Umbraco.Web.WebServices currentView.Path = filename; currentView.Content = contents; - + Attempt attempt; @@ -166,7 +164,7 @@ namespace Umbraco.Web.WebServices if (Math.Max(t.MasterTemplate, 0) != Math.Max(masterTemplateId, 0)) { t.MasterTemplate = Math.Max(masterTemplateId, 0); - pathChanged = true; + pathChanged = true; } } catch (ArgumentException ex) @@ -206,11 +204,9 @@ namespace Umbraco.Web.WebServices public JsonResult SaveScript(string filename, string oldName, string contents) { // sanitize input - script names have an extension - filename = filename - .Replace('\\', '/') - .TrimStart('/'); + filename = CleanFilename(filename); - var svce = (FileService) Services.FileService; + var svce = (FileService)Services.FileService; var script = svce.GetScriptByName(oldName); if (script == null) script = new Script(filename); @@ -223,7 +219,7 @@ namespace Umbraco.Web.WebServices if (svce.ValidateScript(script) == false) return Failed(ui.Text("speechBubbles", "scriptErrorText"), ui.Text("speechBubbles", "scriptErrorHeader"), new FileSecurityException("File '" + filename + "' is not a valid script file.")); - + svce.SaveScript(script); } catch (Exception e) @@ -245,12 +241,18 @@ namespace Umbraco.Web.WebServices public JsonResult SaveStylesheet(string filename, string oldName, string contents) { // sanitize input - stylesheet names have no extension - filename = filename - .Replace('\\', '/') - .TrimStart('/') - .EnsureEndsWith(".css"); + var svce = (FileService)Services.FileService; + + filename = CleanFilename(filename); + oldName = CleanFilename(oldName); + + if (filename != oldName) + { + var stylesheetExists = svce.GetStylesheetByName(filename); + if (stylesheetExists != null) + return Failed(ui.Text("speechBubbles", "cssErrorText"), "A file named '" + filename + ".css' already exists."); + } - var svce = (FileService) Services.FileService; var stylesheet = svce.GetStylesheetByName(oldName); if (stylesheet == null) stylesheet = new Stylesheet(filename); @@ -281,6 +283,14 @@ namespace Umbraco.Web.WebServices }); } + private static string CleanFilename(string filename) + { + return filename + .Replace('\\', '/') + .TrimStart('/') + .EnsureEndsWith(".css"); + } + /// /// Returns a successful message /// diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs index 63dec9111a..b9a62fde8f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs @@ -63,7 +63,7 @@ namespace umbraco.cms.presentation.settings.stylesheet TreeSyncPath = DeepLink.GetTreePathFromFilePath(filename).TrimEnd(".css"); // name derives from path, without the .css extension, clean for xss - NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/'); + NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/').Replace("\\", "/"); if (IsPostBack == false) { @@ -154,4 +154,4 @@ namespace umbraco.cms.presentation.settings.stylesheet protected global::umbraco.uicontrols.CodeArea editorSource; } -} \ No newline at end of file +} From a3cf1d7d582d6be71fc1c0fb11f54a73c651a43f Mon Sep 17 00:00:00 2001 From: Kim Holzmann Date: Thu, 4 Oct 2018 08:25:51 +0200 Subject: [PATCH 051/278] #3138 - Can add a null item to the grid Don't perform submit function if selectedItem is undefined --- .../src/views/propertyeditors/grid/grid.controller.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 dbfbdbdcad..5e08c5e6b4 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 @@ -281,10 +281,12 @@ angular.module("umbraco") availableItems: area.$allowedEditors, event: event, show: true, - submit: function(model) { - $scope.addControl(model.selectedItem, area, index); - $scope.editorOverlay.show = false; - $scope.editorOverlay = null; + submit: function (model) { + if (model.selectedItem) { + $scope.addControl(model.selectedItem, area, index); + $scope.editorOverlay.show = false; + $scope.editorOverlay = null; + } } }; }; From b820baf64350c31808922223cd58f714d965a713 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 8 Oct 2018 14:27:18 +0200 Subject: [PATCH 052/278] Whe moving an item, make sure the current user is registered in Audit instead of the default user (#3150) --- src/Umbraco.Web/Editors/ContentController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1938ac16c2..1a1957cc28 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -546,9 +546,9 @@ namespace Umbraco.Web.Editors EnsureUniqueName(name, content, "name"); - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.GetUserId()); + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); - Services.ContentService.SaveBlueprint(blueprint, Security.GetUserId()); + Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); var notificationModel = new SimpleNotificationModel(); notificationModel.AddSuccessNotification( @@ -755,7 +755,7 @@ namespace Umbraco.Web.Editors return HandleContentNotFound(id, false); } - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); if (publishResult.Success == false) { var notificationModel = new SimpleNotificationModel(); @@ -808,7 +808,7 @@ namespace Umbraco.Web.Editors //if the current item is in the recycle bin if (foundContent.IsInRecycleBin() == false) { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); if (moveResult == false) { //returning an object of INotificationModel will ensure that any pending @@ -818,7 +818,7 @@ namespace Umbraco.Web.Editors } else { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); if (deleteResult == false) { //returning an object of INotificationModel will ensure that any pending @@ -895,7 +895,7 @@ namespace Umbraco.Web.Editors { var toMove = ValidateMoveOrCopy(move); - Services.ContentService.Move(toMove, move.ParentId); + Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); From 7db13960e9c665ea69b9f177bc6f7a8314e0b330 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:32:12 +0100 Subject: [PATCH 053/278] Adds in GetAllTypes to ApplicationTreeService --- src/Umbraco.Core/Services/IApplicationTreeService.cs | 11 ++++++++++- src/Umbraco.Web/Services/ApplicationTreeService.cs | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/IApplicationTreeService.cs b/src/Umbraco.Core/Services/IApplicationTreeService.cs index 5b6976c021..98c3047fea 100644 --- a/src/Umbraco.Core/Services/IApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/IApplicationTreeService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -41,6 +42,9 @@ namespace Umbraco.Core.Services /// Returns a ApplicationTree Array IEnumerable GetAll(); + + IEnumerable GetAllTypes(); + /// /// Gets the application tree for the applcation with the specified alias /// @@ -113,6 +117,11 @@ namespace Umbraco.Core.Services throw new System.NotImplementedException(); } + public IEnumerable GetAllTypes() + { + throw new System.NotImplementedException(); + } + /// /// Gets the application tree for the applcation with the specified alias /// diff --git a/src/Umbraco.Web/Services/ApplicationTreeService.cs b/src/Umbraco.Web/Services/ApplicationTreeService.cs index abd9f08a53..08a17187e7 100644 --- a/src/Umbraco.Web/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Web/Services/ApplicationTreeService.cs @@ -21,6 +21,7 @@ namespace Umbraco.Web.Services private readonly ILogger _logger; private readonly CacheHelper _cache; private Lazy> _allAvailableTrees; + private IEnumerable _treeTypes; internal const string TreeConfigFileName = "trees.config"; private static string _treeConfig; private static readonly object Locker = new object(); @@ -251,6 +252,11 @@ namespace Umbraco.Web.Services return GetAppTrees().OrderBy(x => x.SortOrder); } + public IEnumerable GetAllTypes() + { + return _treeTypes ?? (_treeTypes = GetAll().Select(x => x.GetRuntimeType())); + } + /// /// Gets the application tree for the applcation with the specified alias /// From 6c7c510eb98495db0ddf94fd7200c2953be93fab Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:33:14 +0100 Subject: [PATCH 054/278] Initial WIP of grouping trees for settings section - needs tidying --- .../Trees/ApplicationTreeController.cs | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index d489421353..0b9ca64ca9 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -8,6 +9,7 @@ using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.Composing; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; @@ -20,6 +22,14 @@ namespace Umbraco.Web.Trees [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { + private static readonly Lazy>> CoreTrees + = new Lazy>>(() => + Current.Services.ApplicationTreeService.GetAllTypes() + .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) + .GroupBy(x => x.TreeGroup) + .ToList()); + + /// /// Returns the tree nodes for an application /// @@ -29,10 +39,12 @@ namespace Umbraco.Web.Trees /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) + public async Task> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { application = application.CleanForXss(); + var rootNodeGroups = new List(); + if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); @@ -56,7 +68,10 @@ namespace Umbraco.Web.Trees //this will be null if it cannot convert to ta single root section if (result != null) - return result; + { + rootNodeGroups.Add(result); + return rootNodeGroups; + } } var collection = new TreeNodeCollection(); @@ -71,9 +86,90 @@ namespace Umbraco.Web.Trees } } - var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); - multiTree.Name = Services.TextService.Localize("sections/"+ application); - return multiTree; + //Don't apply fancy grouping logic futher down, if we are not 'settings' section + if(application != Constants.Applications.Settings) + { + var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); + multiTree.Name = Services.TextService.Localize("sections/" + application); + + rootNodeGroups.Add(multiTree); + return rootNodeGroups; + } + + //For settings section only + //Group trees by [CoreTree] attribute + + //Core Trees contains all trees for all sections/applications + foreach(var treeSectionGroup in CoreTrees.Value) + { + var treeGroupName = treeSectionGroup.Key; + if (treeGroupName == null) + { + //This is third party trees + //Where user definied or [CoreTree] with no group is set + var thirdPartyNodes = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach(var thirdPartyTree in treeSectionGroup) + { + //Item1 is the type + var thirdPartyType = thirdPartyTree.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if(thirdPartyTreeNode != null) + { + //Add to a new list/collection + thirdPartyNodes.Add(thirdPartyTreeNode); + } + } + } + + //Compared all 'null' grouped trees with appTreeTypes + //Create third party node collection + var thirdPartyRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, thirdPartyNodes); + thirdPartyRoot.Name = "Third Party WARREN"; + + rootNodeGroups.Add(thirdPartyRoot); + } + else + { + var groupNodeCollection = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach (var thirdPartyTree in treeSectionGroup) + { + //Item1 is the type + var thirdPartyType = thirdPartyTree.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if (thirdPartyTreeNode != null) + { + //Add to a new list/collection + groupNodeCollection.Add(thirdPartyTreeNode); + } + } + } + + //Compared all 'null' grouped trees with appTreeTypes + //Create third party node collection + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = treeGroupName; + + rootNodeGroups.Add(groupRoot); + } + } + + return rootNodeGroups; } /// From 5a551bd3d60be735b2e3ae406d87ea3df19729a6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:34:11 +0100 Subject: [PATCH 055/278] Changed needed as GetApplicationTrees returns an IEnumerable of SectionRootNodes now --- src/Umbraco.Web/Editors/SectionController.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 03921b396c..7c3eb94059 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -50,9 +50,13 @@ namespace Umbraco.Web.Editors { //get the first tree in the section and get it's root node route path var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - section.RoutePath = sectionTrees.IsContainer == false || sectionTrees.Children.Count == 0 - ? sectionTrees.RoutePath - : sectionTrees.Children[0].RoutePath; + + //Root node trees are now in collection + var firstTree = sectionTrees.FirstOrDefault(); + + section.RoutePath = firstTree.IsContainer == false || firstTree.Children.Count == 0 + ? firstTree.RoutePath + : firstTree.Children[0].RoutePath; } } From b3f4e9da8ffb07652bb386105e5139d373b4a119 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:36:14 +0100 Subject: [PATCH 056/278] JS tweaks needed to deal with a collection of SectionRootNodes returning from the API --- .../src/common/services/tree.service.js | 92 ++++++++++--------- .../src/controllers/navigation.controller.js | 14 ++- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index d5d2093d3b..a5bbf2f886 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -15,21 +15,21 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent // as a nodeid reference instead of a variable with a getParent() method. var treeCache = {}; - + var standardCssClass = 'icon umb-tree-icon sprTree'; function getCacheKey(args) { //if there is no cache key they return null - it won't be cached. if (!args || !args.cacheKey) { return null; - } + } var cacheKey = args.cacheKey; cacheKey += "_" + args.section; return cacheKey; } - return { + return { /** Internal method to return the tree cache */ _getTreeCache: function() { @@ -70,10 +70,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } }); }, - + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { - //if no level is set, then we make it 1 + //if no level is set, then we make it 1 var childLevel = (level ? level : 1); //set the section if it's not already set if (!parentNode.section) { @@ -91,13 +91,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var funcParent = function() { return parentNode; }; + for (var i = 0; i < treeNodes.length; i++) { var treeNode = treeNodes[i]; treeNode.level = childLevel; - //create a function to get the parent node, we could assign the parent node but + //create a function to get the parent node, we could assign the parent node but // then we cannot serialize this entity because we have a cyclical reference. // Instead we just make a function to return the parentNode. treeNode.parent = funcParent; @@ -108,17 +109,17 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //if there is not route path specified, then set it automatically, //if this is a tree root node then we want to route to the section's dashboard if (!treeNode.routePath) { - + if (treeNode.metaData && treeNode.metaData["treeAlias"]) { //this is a root node - treeNode.routePath = section; + treeNode.routePath = section; } else { var treeAlias = this.getTreeAlias(treeNode); treeNode.routePath = section + "/" + treeAlias + "/edit/" + treeNode.id; } } - + //now, format the icon data if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); @@ -155,10 +156,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @description * Determines if the current tree is a plugin tree and if so returns the package folder it has declared * so we know where to find it's views, otherwise it will just return undefined. - * + * * @param {String} treeAlias The tree alias to check */ - getTreePackageFolder: function(treeAlias) { + getTreePackageFolder: function(treeAlias) { //we determine this based on the server variables if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && @@ -167,7 +168,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { return item.alias === treeAlias; }); - + return found ? found.packageFolder : undefined; } return undefined; @@ -181,7 +182,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @description * Clears the tree cache - with optional cacheKey, optional section or optional filter. - * + * * @param {Object} args arguments * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs * @param {String} args.section optional section alias - clear tree for a given section @@ -205,7 +206,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.cacheKey) { throw "args.cacheKey is required if args.childrenOf is supplied"; } - //this will clear out all children for the parentId passed in to this parameter, we'll + //this will clear out all children for the parentId passed in to this parameter, we'll // do this by recursing and specifying a filter var self = this; this.clearCache({ @@ -238,7 +239,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //set the result to the filtered data treeCache[args.cacheKey] = result; } - else { + else { //remove the cache treeCache = _.omit(treeCache, args.cacheKey); } @@ -261,7 +262,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return k.endsWith("_" + args.section); }); treeCache = _.omit(treeCache, toRemove2); - } + } } }, @@ -285,7 +286,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!args.node) { throw "No node defined on args object for loadNodeChildren"; } - + this.removeChildNodes(args.node); args.node.loading = true; @@ -312,7 +313,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //in case of error, emit event eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); - //stop show the loading indicator + //stop show the loading indicator args.node.loading = false; //tell notications about the error @@ -342,9 +343,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS throw "Cannot remove a node that doesn't have a parent"; } //remove the current item from it's siblings - treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); + treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); }, - + /** * @ngdoc method * @name umbraco.services.treeService#removeChildNodes @@ -352,7 +353,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Removes all child nodes from a given tree node + * Removes all child nodes from a given tree node * @param {object} treeNode the node to remove children from */ removeChildNodes : function(treeNode) { @@ -426,7 +427,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (found) { return found; } - + //check each child of this node if (!treeNode.children) { return null; @@ -442,7 +443,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } } - + //not found return found === undefined ? null : found; }, @@ -464,9 +465,9 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //all root nodes have metadata key 'treeAlias' var root = null; - var current = treeNode; + var current = treeNode; while (root === null && current) { - + if (current.metaData && current.metaData["treeAlias"]) { root = current; } @@ -491,7 +492,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node + * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node * @param {object} treeNode to retrive tree alias from */ getTreeAlias : function(treeNode) { @@ -509,7 +510,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * gets the tree, returns a promise + * gets the tree, returns a promise * @param {object} args Arguments * @param {string} args.section Section alias * @param {string} args.cacheKey Optional cachekey @@ -525,7 +526,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } var cacheKey = getCacheKey(args); - + //return the cache if it exists if (cacheKey && treeCache[cacheKey] !== undefined) { return $q.when(treeCache[cacheKey]); @@ -540,8 +541,13 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS alias: args.section, root: data }; - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); + + for (var i = 0; i < result.root.length; i++) { + var group = result.root[i]; + + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(group, group.children, args.section); + } //cache this result if a cache key is specified - generally a cache key should ONLY // be specified for application trees, dialog trees should not be cached. @@ -584,7 +590,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return data; }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#getChildren @@ -592,7 +598,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * Gets the children from the server for a given node + * Gets the children from the server for a given node * @param {object} args Arguments * @param {object} args.node tree node object to retrieve the children for * @param {string} args.section current section alias @@ -618,7 +624,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return $q.when(data); }); }, - + /** * @ngdoc method * @name umbraco.services.treeService#reloadNode @@ -639,7 +645,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!node.section) { throw "cannot reload a single node without an assigned node.section"; } - + //set the node to loading node.loading = true; @@ -663,7 +669,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //just update as per normal - this means styles, etc.. won't be applied _.extend(node.parent().children[index], found); } - + //set the node loading node.parent().children[index].loading = false; //return @@ -684,12 +690,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @function * * @description - * This will return the current node's path by walking up the tree + * This will return the current node's path by walking up the tree * @param {object} node Tree node to retrieve path for */ getPath: function(node) { if (!node) { - throw "node cannot be null"; + throw "node cannot be null"; } if (!angular.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; @@ -698,7 +704,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var reversePath = []; var current = node; while (current != null) { - reversePath.push(current.id); + reversePath.push(current.id); if (current.metaData && current.metaData["treeAlias"]) { current = null; } @@ -710,7 +716,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS }, syncTree: function(args) { - + if (!args) { throw "No args object defined for syncTree"; } @@ -733,7 +739,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!root) { throw "Could not get the root tree node based on the node passed in"; } - + //now we want to loop through the ids in the path, first we'll check if the first part //of the path is the root node, otherwise we'll search it's children. var currPathIndex = 0; @@ -748,7 +754,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS currPathIndex = 1; } } - + //now that we have the first id to lookup, we can start the process var self = this; @@ -778,7 +784,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } } else { - //couldn't find it in the + //couldn't find it in the return self.loadNodeChildren({ node: node, section: node.section }).then(function (children) { //ok, got the children, let's find it var found = self.getChildNode(node, args.path[currPathIndex]); @@ -810,7 +816,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS return doSync(); } - + }; } diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 1f6f2c75b8..dcc17612b3 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -184,9 +184,15 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Listen for section state changes evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - var f = args; - if (args.value.root && args.value.root.metaData.containsTrees === false) { - $rootScope.emptySection = true; + if (args.value.root.length > 0) + { + for (var i = 0; i < args.value.root.length; i++) { + var group = args.value.root[i]; + + if(group.metaData.containsTrees === false){ + $rootScope.emptySection = true; + } + } } else { $rootScope.emptySection = false; @@ -422,7 +428,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //this reacts to the options item in the tree //TODO: migrate to nav service - //TODO: is this used? + //TODO: is this used? $scope.searchShowMenu = function (ev, args) { //always skip default args.skipDefault = true; From c306bdea47a2ed4777ab7886727c9c7d9c67854f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 13:36:59 +0100 Subject: [PATCH 057/278] Updates umb-tree directive to loop over the uodated WebAPI collection of SectionRootNodes --- .../src/views/components/tree/umb-tree.html | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html index 74c1dc2c99..3713312968 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html @@ -1,19 +1,21 @@ \ No newline at end of file From be42783616a994272f2b28002ccb9c5de864a625 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 14:10:11 +0100 Subject: [PATCH 058/278] Remove dupe code & order by descending (still need to specify own sort order) --- .../Trees/ApplicationTreeController.cs | 90 +++++++------------ 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 0b9ca64ca9..7eeb19893e 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,6 +27,7 @@ namespace Umbraco.Web.Trees Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) + .OrderByDescending(x => x.Key) .ToList()); @@ -103,70 +104,41 @@ namespace Umbraco.Web.Trees foreach(var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; + + var groupNodeCollection = new TreeNodeCollection(); + + //Only add trees to a new collection if they are from 'settings' + foreach (var treeItem in treeSectionGroup) + { + //Item1 tuple - is the type from all tree types + var treeItemType = treeItem.Item1; + + var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == treeItemType); + if (findAppTree != null) + { + //Now we need to get the 'TreeNode' which is in 'collection' + var treeItemNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + + if (treeItemNode != null) + { + //Add to a new list/collection + groupNodeCollection.Add(treeItemNode); + } + } + } + + //If treeGroupName == null then its third party if (treeGroupName == null) { - //This is third party trees - //Where user definied or [CoreTree] with no group is set - var thirdPartyNodes = new TreeNodeCollection(); - - //Only add trees to a new collection if they are from 'settings' - foreach(var thirdPartyTree in treeSectionGroup) - { - //Item1 is the type - var thirdPartyType = thirdPartyTree.Item1; - - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); - if (findAppTree != null) - { - //Now we need to get the 'TreeNode' which is in 'collection' - var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); - - if(thirdPartyTreeNode != null) - { - //Add to a new list/collection - thirdPartyNodes.Add(thirdPartyTreeNode); - } - } - } - - //Compared all 'null' grouped trees with appTreeTypes - //Create third party node collection - var thirdPartyRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, thirdPartyNodes); - thirdPartyRoot.Name = "Third Party WARREN"; - - rootNodeGroups.Add(thirdPartyRoot); + //This is used for the localisation key + //treeHeaders/thirdPartyGroup + treeGroupName = "thirdPartyGroup"; } - else - { - var groupNodeCollection = new TreeNodeCollection(); - //Only add trees to a new collection if they are from 'settings' - foreach (var thirdPartyTree in treeSectionGroup) - { - //Item1 is the type - var thirdPartyType = thirdPartyTree.Item1; + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == thirdPartyType); - if (findAppTree != null) - { - //Now we need to get the 'TreeNode' which is in 'collection' - var thirdPartyTreeNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); - - if (thirdPartyTreeNode != null) - { - //Add to a new list/collection - groupNodeCollection.Add(thirdPartyTreeNode); - } - } - } - - //Compared all 'null' grouped trees with appTreeTypes - //Create third party node collection - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); - groupRoot.Name = treeGroupName; - - rootNodeGroups.Add(groupRoot); - } + rootNodeGroups.Add(groupRoot); } return rootNodeGroups; From 2e135d2006212df799765552bc0e916b9711cee9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 4 Oct 2018 20:27:06 +0200 Subject: [PATCH 059/278] Register an audit on the parent node when sorting child nodes. --- src/Umbraco.Core/Services/ContentService.cs | 13 ++++++++----- 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 ++ src/Umbraco.Web/Editors/ContentController.cs | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2f8b9c8340..9b0de40090 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2060,12 +2060,15 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); - var items = ids.Select(x => allContent[x]); + if (allContent.Any() == false) + { + return false; + } + var items = ids.Select(x => allContent[x]).ToArray(); using (var uow = UowProvider.GetUnitOfWork()) { - var asArray = items.ToArray(); - var saveEventArgs = new SaveEventArgs(asArray); + var saveEventArgs = new SaveEventArgs(items); if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) { uow.Commit(); @@ -2075,7 +2078,7 @@ namespace Umbraco.Core.Services var repository = RepositoryFactory.CreateContentRepository(uow); var i = 0; - foreach (var content in asArray) + foreach (var content in items) { //If the current sort order equals that of the content //we don't need to update it, so just increment the sort order @@ -2122,7 +2125,7 @@ namespace Umbraco.Core.Services _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); } - Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); + Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); uow.Commit(); } } diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 71195be6f8..0e9fc09bcd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -147,6 +147,7 @@ Brugeren har tilbagerullet indholdet til en tidligere tilstand Brugeren har sendt indholdet til udgivelse Brugeren har sendt indholdet til oversættelse + Brugeren har sorteret de underliggende sider Kopieret Udgivet Flyttet @@ -156,6 +157,7 @@ Indhold tilbagerullet Sendt til udgivelse Sendt til oversættelse + Sorteret For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f12d509416..477bce0931 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -153,6 +153,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -162,6 +163,7 @@ Rollback Send To Publish Send To Translation + Sort To change the document type for the selected content, first select from the list of valid types for this location. 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 c6a6aadd77..b1fa2b84b3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -153,6 +153,7 @@ Content rollback performed by user Content Send To Publish performed by user Content Send To Translation performed by user + Sort child items performed by user Copy Publish Move @@ -162,6 +163,7 @@ Rollback Send To Publish Send To Translation + Sort diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1a1957cc28..6268759e29 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -871,7 +871,7 @@ namespace Umbraco.Web.Editors var contentService = Services.ContentService; // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder) == false) + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) { LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); From b2bc259bd567f18343f8cf024664b051d420c698 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 8 Oct 2018 14:19:52 +0100 Subject: [PATCH 060/278] Adds in lang keys --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/he.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/it.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 3 +++ src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 3 +++ src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 3 +++ 20 files changed, 60 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 3471203e76..e554b45672 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -986,6 +986,9 @@ Oprávnění Uživatele Typy Uživatelů Uživatelé + + + Nová aktualizace je připrvena diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index e0dd0973f6..f1ecff48aa 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1452,6 +1452,9 @@ Mange hilsner fra Umbraco robotten Brugertilladelser Bruger Typer Brugere + + + Ny opdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index c701e439ea..c02da3924d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -979,6 +979,9 @@ Ihr freundlicher Umbraco-Robot Berechtigungen Benutzertypen Benutzer + + + Neues Update verfügbar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 5b8dc0982c..d0c9685304 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1831,6 +1831,9 @@ To manage your website, simply open the Umbraco back office and start adding con Templates Analytics Users + Settings + Templating + Third Party New update ready 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 e449e798f0..01a30d74ae 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1859,6 +1859,9 @@ To manage your website, simply open the Umbraco back office and start adding con Templates Analytics Users + Settings + Templating + Third Party New update ready diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index dda74e00ed..717506f0a6 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -1520,6 +1520,9 @@ Plantillas Analíticas Usuarios + + + Existe una nueva actualización diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 6a1f9a58e7..d88fa30706 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -1818,6 +1818,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Analytique Utilisateurs Analytique + + + Nouvelle mise à jour disponible diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 911e0f4131..f22f1c3f19 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -888,6 +888,9 @@ To manage your website, simply open the Umbraco back office and start adding con הרשאות משתמש משתמש מקליד משתמש + + + עידכון חדש זמין diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml index 1f661000b6..89a03721ba 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/it.xml @@ -878,6 +878,9 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Permessi Utente Tipi di Utente Utenti + + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index d3a4a5c27a..561e6ac1fc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -1185,6 +1185,9 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの権限 ユーザータイプ ユーザー + + + 新しい更新があります diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index a7ab31efc6..b7aa8b5bb5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -871,6 +871,9 @@ 사용자권한 사용자 유형 사용자 + + + 새 업데이트가 준비되었습니다. diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 72d4afbeca..c7b15f678e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -1296,6 +1296,9 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Stylesheets Sjablonen Analytics + + + Nieuwe update beschikbaar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 5f679a994a..bfb5fe74db 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -1496,6 +1496,9 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Analizy Częściowe Widoki Pliki Makro Częściowych Widoków + + + Aktualizacja jest gotowa diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml index a62b51f76f..47f87291e1 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml @@ -854,6 +854,9 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Permissões de usuário Tipos de Usuários Usuários + + + Nova atualização pronta diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 6cdfa16fcd..21b3cdce18 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -1839,6 +1839,9 @@ Стили CSS Шаблоны Пользователи + + + Доступны обновления diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 83e1405560..328169ade3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -901,6 +901,9 @@ Användarrättigheter Användartyper Användare + + + Ny uppdatering tillgänglig diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index a69300dbfb..79af1b31e8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -1073,6 +1073,9 @@ To manage your website, simply open the Umbraco back office and start adding con Stil dosyaları Şablonlar Analitikler + + + Yeni bir güncelleme geldi diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index 64ad3eab1c..80d01ac1af 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -1242,6 +1242,9 @@ Users 分部视图 分部视图宏文件 + + + 有可用更新 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 235177c712..be99f6b561 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -971,6 +971,9 @@ Vennlig hilsen Umbraco roboten Brukertillatelser Brukertyper typer Brukere + + + Ny oppdatering er klar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index a4c6fc7de5..93f6f5a10f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -1178,6 +1178,9 @@ 樣式表 範本 統計 + + + 有可用更新 From 3977d24eb2c8583d3ac2499a582c6d0d9f394412 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 14:29:09 +0100 Subject: [PATCH 061/278] Button styling on inactive user profile & clean up user view (#3170) --- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../components/users/umb-user-details.less | 122 +++++++++++++++++ .../src/views/users/user.html | 4 +- .../src/views/users/views/user/details.html | 123 +++++++++--------- 4 files changed, 187 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 2eaabe7842..eaf0b60707 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -146,6 +146,7 @@ @import "components/umb-mini-editor.less"; @import "components/users/umb-user-cards.less"; +@import "components/users/umb-user-details.less"; @import "components/users/umb-user-group-picker-list.less"; @import "components/users/umb-user-group-preview.less"; @import "components/users/umb-user-preview.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less new file mode 100644 index 0000000000..837506f312 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less @@ -0,0 +1,122 @@ +.umb-user-details-avtar { + margin-bottom: 20px; + padding-bottom: 20px; + border-bottom: 1px solid #d8d7d9; +} + +div.umb-user-details-actions > div { + margin-bottom: 20px; +} + +.umb-user-details-actions .umb-button { + margin-bottom: 20px; +} + +.umb-user-details-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-user-details-view-wrapper { + padding: 20px 60px; +} + +@media (max-width: 768px) { + + .umb-user-details-view-wrapper { + padding: 0; + } +} + +.umb-user-details-section { + margin-bottom: 40px; +} +.umb-user-details-details { + display: flex; +} + +a.umb-user-details-details__back-link { + font-weight: bold; + color: @black; +} + +.umb-user-details-details__back-link:hover { + color: @gray-4; + text-decoration: none; +} + + +@sidebarwidth: 350px; // Width of sidebar. Ugly hack because of old version of Less + +.umb-user-details-details__main-content { + flex: 1 1 auto; + margin-right: 30px; + width: calc(~'100%' - ~'@{sidebarwidth}' - ~'30px'); // Make sure that the main content area doesn't gets affected by inline styling +} + +.umb-user-details-details__main-content .umb-node-preview-add { + max-width: 100%; +} + + +.umb-user-details-details__sidebar { + flex: 0 0 @sidebarwidth; +} + +@media (max-width: 768px) { + + .umb-user-details-details { + flex-direction: column; + } + + .umb-user-details-details__main-content { + flex: 1 1 auto; + width: 100%; + margin-bottom: 30px; + margin-right: 0; + } + + .umb-user-details-details__sidebar { + flex: 1 1 auto; + width: 100%; + } +} + +.umb-user-details-details__section { + background: @gray-10; + padding: 20px; + margin-bottom: 20px; + border-radius: 3px; + border: 1px solid @gray-8; +} + +.umb-user-details-details__section-title { + font-size: 17px; + font-weight: bold; + color: @black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-user-details-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-user-details-details__information-item { + margin-bottom: 10px; + font-size: 13px; +} + +.umb-user-details-details__information-item-label { + color: @black; + font-weight: bold; +} + +.umb-user-details-details__information-item-content { + word-break: break-word; +} + diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index 32c1c3d641..3e7b2b8d8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -15,7 +15,7 @@ -
+
-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 7c93f1fd84..15a75c30b2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -1,6 +1,6 @@ -
+
-
+
@@ -95,8 +95,8 @@ Add @@ -121,9 +121,9 @@ Add @@ -165,12 +165,12 @@
-
+
-
+
-
+
-
+
-
+
-
+
- -
+
- - - - - - +
+ + +
+
+ + +
@@ -293,26 +294,26 @@
-
-
+
+
Status:
-
+
{{model.user.userDisplayState.name}}
-
+
+ type="text" + class="input-block-level" + localize="placeholder" + placeholder="@placeholders_enterMessage" + ng-model="model.resendInviteMessage" + rows="4"> +
-
-
+
+
Last login:
-
+
{{ model.user.formattedLastLogin }} {{ model.user.name | umbWordLimit:1 }} has not logged in yet
-
-
+
+
Failed login attempts:
-
+
{{ model.user.failedPasswordAttempts }}
-
-
+
+
Last lockout date:
-
+
{{ model.user.name | umbWordLimit:1 }} hasn't been locked out @@ -354,11 +355,11 @@
-
-
+
+
Password is last changed:
-
+
The password hasn't been changed @@ -366,20 +367,20 @@
-
-
+
+
User is created:
-
+
{{ model.user.formattedCreateDate }}
-
-
+
+
User is last updated:
-
+
{{ model.user.formattedUpdateDate }}
From b2d1a48c7ad157953ef91250f34a6f78f86aa02f Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Oct 2018 08:19:36 +0200 Subject: [PATCH 062/278] Added null check for EmailValidator value, fixed js error. --- src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs | 2 +- .../src/common/directives/components/content/edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs index 762b6dd7dd..4df11e4f60 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.PropertyEditors.Validators /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { - var asString = value.ToString(); + var asString = value == null ? "" : value.ToString(); var emailVal = new EmailAddressAttribute(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69c871f5ce..084b72ed97 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -107,7 +107,7 @@ evts.push(eventsService.on("editors.documentType.saved", function (name, args) { // if this content item uses the updated doc type we need to reload the content item - if (args && args.documentType && args.documentType.key === content.documentType.key) { + if (args && args.documentType && args.documentType.key === $scope.content.documentType.key) { loadContent(); } })); From 94922ccbd2118e0d51088b1751ce50ce1f2df7e2 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 9 Oct 2018 08:51:02 +0200 Subject: [PATCH 063/278] #3141 fixed the problem differently after input from @markadrake --- .../src/common/services/navigation.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8fb7137e9a..e617f62ed4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -73,6 +73,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("showSearchResults", false); appState.setGlobalState("stickyNavigation", false); appState.setGlobalState("showTray", false); + appState.setMenuState("currentNode", null); if (appState.getGlobalState("isTablet") === true) { appState.setGlobalState("showNavigation", false); @@ -660,10 +661,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo */ hideDialog: function (showMenu) { - setMode("default"); - - if(showMenu){ + if (showMenu) { this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") }); + } else { + setMode("default"); } }, /** From cc52652481d77db4a1b42a8861744178e0c5602e Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Oct 2018 09:44:44 +0200 Subject: [PATCH 064/278] Added some comments detailing the hack that is in place for the where clause. --- .../Repositories/Implement/DocumentRepository.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6fc3653db3..d888f452ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -700,6 +700,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Sql filterSql = null; + // Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name + // if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead. + // The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same + // we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names. var temp = Query().Where(x => x.Name.Contains("foo")); var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; @@ -709,8 +713,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var filterClause in filter.GetWhereClauses()) { // fixme - is this the right way of doing it??? + + // var where = filterClause.Item1.Split(' ')[0] == clause - // normally, this would be the field alias of the coalesce result between ContentVersionCulture and NodeDto names, however + // normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql : filterClause.Item1; From f63a9e93b0447d45003b911042de677356c41e77 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 9 Oct 2018 08:53:40 +0100 Subject: [PATCH 065/278] Merge remote-tracking branch 'origin/temp8' into temp8-3087-tree-grouping # Conflicts: # src/Umbraco.Web.UI/Umbraco/config/lang/en.xml # src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/Umbraco/config/lang/es.xml # src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml # src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml # src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml # src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml # src/Umbraco.Web/Trees/DictionaryTreeController.cs --- src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 1026 ------------- .../umbraco/config/lang/zh_tw.xml | 1346 ----------------- 2 files changed, 2372 deletions(-) delete mode 100644 src/Umbraco.Web.UI/umbraco/config/lang/nb.xml delete mode 100644 src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml deleted file mode 100644 index be99f6b561..0000000000 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ /dev/null @@ -1,1026 +0,0 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Angi domene - Revisjoner - Bla gjennom - Skift dokumenttype - Kopier - Opprett - Opprett pakke - Slett - Deaktiver - Tøm papirkurv - Eksporter dokumenttype - Importer dokumenttype - Importer pakke - Rediger i Canvas - Logg av - Flytt - Varslinger - Offentlig tilgang - Publiser - Avpubliser - Oppdater noder - Republiser hele siten - Gjenopprett - Rettigheter - Reverser - Send til publisering - Send til oversetting - Sorter - Oversett - Oppdater - - - Ingen tilgang. - Legg til domene - Fjern - Ugyldig node. - Ugyldig domeneformat. - Domene er allerede tilknyttet. - Språk - Domene - Domene '%0%' er nå opprettet og tilknyttet siden - Domenet '%0%' er nå slettet - Domenet '%0%' er allerede tilknyttet - Domenet '%0%' er nå oppdatert - eller rediger eksisterende domener -
Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
- Arv - Språk - Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]> - Domener - - - Viser for - - - Velg - Velg gjeldende mappe - Gjør noe annet - Fet - Reduser innrykk - Sett inn skjemafelt - Sett inn grafisk overskrift - Rediger HTML - Øk innrykk - Kursiv - Midtstill - Juster tekst venstre - Juster tekst høyre - Sett inn lenke - Sett inn lokal lenke (anker) - Punktmerking - Nummerering - Sett inn makro - Sett inn bilde - Rediger relasjoner - Tilbake til listen - Lagre - Lagre og publiser - Lagre og planlegge - Lagre og send til publisering - Forhåndsvis - Forhåndsvisning er deaktivert siden det ikke er angitt noen mal - Velg formattering - Vis stiler - Sett inn tabell - - - For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering. - Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre. - Innholdet har blitt republisert. - Nåværende egenskap - Nåværende type - Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen. - Dokumenttype endret - Overfør egenskaper - Overfør til egenskap - Ny mal - Ny type - ingen - Innhold - Velg ny dokumenttype - Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført: - til - Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang. - Kun andre dokumenttyper som er gyldige for denne plasseringen vises. - - - Publisert - Om siden - Alias - (hvordan du ville beskrevet bildet over telefon) - Alternative lenker - Klikk for å redigere denne noden - Opprettet av - Opprinnelig forfatter - Oppdatert av - Opprettet den - Tidspunkt for opprettelse - Dokumenttype - Redigerer - Utløpsdato - Denne noden er endret siden siste publisering - Denne noden er enda ikke publisert - Sist publisert - Det er ingen elementer å vise i listen. - Mediatype - Link til media - Medlemsgruppe - Rolle - Medlemstype - Ingen dato valgt - Sidetittel - Egenskaper - Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert - Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer - Publisert - Publiseringsstatus - Publiseringsdato - Dato for avpublisering - Fjern dato - Sorteringsrekkefølgen er oppdatert - Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger. - Statistikk - Tittel (valgfri) - Alternativ tekst (valgfri) - Type - Avpubliser - Sist endret - Tidspunkt for siste endring - Fjern fil - Lenke til dokument - Medlem av gruppe(ne) - Ikke medlem av gruppe(ne) - Undersider - Åpne i vindu - - - Klikk for å laste opp - Slipp filene her... - - - Opprett et nytt medlem - Alle medlemmer - - - Hvor ønsker du å oprette den nye %0% - Opprett under - Velg en type og skriv en tittel - "dokumenttyper".]]> - "mediatyper".]]> - - - Til ditt nettsted - - Skjul - Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt - er åpnet i nytt vindu - Omstart - Besøk - Velkommen - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Navn på lokal link - Rediger domener - Lukk dette vinduet - Er du sikker på at du vil slette - Er du sikker på at du vil deaktivere - Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er) - Er du sikker på at du vil forlate Umbraco? - Er du sikker? - Klipp ut - Rediger ordboksnøkkel - Rediger språk - Sett inn lokal link - Sett inn spesialtegn - Sett inn grafisk overskrift - Sett inn bilde - Sett inn lenke - Sett inn makro - Sett inn tabell - Sist redigert - Lenke - Intern link: - Ved lokal link, sett inn "#" foran link - Åpne i nytt vindu? - Makroinnstillinger - Denne makroen har ingen egenskaper du kan endre - Lim inn - Endre rettigheter for - Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår - Papirkurven er nå tom - Når elementer blir slettet fra papirkurven vil de være slettet for alltid - regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]> - Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url' - Fjern makro - Obligatorisk - Nettstedet er indeksert - Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert. - Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert. - Antall kolonner - Antall rader - Sett en plassholder-ID
Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
- Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]> - Klikk på bildet for å se det i full størrelse - Velg punkt - Se buffret node - - - %0%' under.
Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
- Språk - - - Skriv inn ditt brukernavn - Skriv inn ditt passord - Navngi %0%... - Skriv inn navn... - Søk... - Filtrer... - Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)... - - - Tillat på rotnivå - Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv - Tillatte underordnede noder - Sammensetting av dokumenttyper - Opprett - Slett arkfane - Beskrivelse - Ny arkfane - Arkfane - Miniatyrbilde - Aktiver listevisning - Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet - Gjeldende listevisning - Den aktive listevisningsdatatypen - Opprett brukerdefinert listevisning - Fjern brukerdefinert listevisning - - - Legg til forhåndsverdi - Database datatype - Kontrollelement GUID - Kontrollelement - Knapper - Aktiver avanserte instillinger for - Aktiver kontektsmeny - Maksimum standard størrelse på innsatte bilder - Beslektede stilark - Vis etikett - Bredde og høyde - - - Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil: - Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann) - %0% finnes allerede - Det var feil i dokumentet: - Det var feil i skjemaet: - Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn - %0% må være et heltall - %0% under %1% er obligatorisk - %0% er obligatorisk - %0% under %1% er ikke i et korrekt format - %0% er ikke i et korrekt format - - - Filtypen er deaktivert av administrator - NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet. - Fyll ut både alias og navn på den nye egenskapstypen! - Det er et problem med lese/skrive rettighetene til en fil eller mappe - Tittel mangler - Type mangler - Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette? - Feil i python-skriptet - Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil - Startnode er slettet. Kontakt din administrator - Du må markere innhold før du kan endre stil - Det er ingen aktive stiler eller formateringer på denne siden - Sett markøren til venstre i de 2 cellene du ønsker å slå sammen - Du kan ikke dele en celle som allerede er delt. - Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen. - - - Om - Handling - Muligheter - Legg til - Alias - Er du sikker? - Ramme - av - Avbryt - Cellemargin - Velg - Lukk - Lukk vindu - Kommentar - Bekreft - Behold proposjoner - Fortsett - Kopier - Opprett - Database - Dato - Standard - Slett - Slettet - Sletter... - Design - Dimensjoner - Ned - Last ned - Rediger - Endret - Elementer - E-post - Feil - Finn - Høyde - Hjelp - Ikon - Importer - Indre margin - Sett inn - Installer - Justering - Språk - Layout - Laster - Låst - Logg inn - Logg ut - Logg ut - Makro - Flytt - Mer - Navn - Ny - Neste - Nei - av - OK - Åpne - eller - Passord - Sti - Plassholder ID - Ett øyeblikk... - Forrige - Egenskaper - E-post som innholdet i skjemaet skal sendes til - Papirkurv - Gjenværende - Gi nytt navn - Forny - Påkrevd - Prøv igjen - Rettigheter - Søk - Server - Vis - Hvilken side skal vises etter at skjemaet er sendt - Størrelse - Sorter - Send - Type - Søk... - Opp - Oppdater - Oppgrader - Last opp - Url - Bruker - Brukernavn - Verdi - Visning - Velkommen... - Bredde - Ja - Mappe - Søkeresultater - Sorter - Avslutt sortering - Eksempel - Bytt passord - til - Listevisning - Lagrer... - nåværende - Innbygging - Hent - valgt - - - Bakgrunnsfarge - Fet - Tekstfarge - Skrifttype - Tekst - - - Side - - - Installasjonsprogrammet kan ikke koble til databasen - Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt. - Din database er funnet og identifisert som - Databasekonfigurasjon - installer-knappen for å installere Umbraco %0% databasen]]> - Neste for å fortsette.]]> - Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

Klikk prøv på nytt når du er ferdig.
Mer informasjon om redigering av web.config her.

]]>
- Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> - Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

]]>
- Trykk Neste for å fortsette.]]> - neste for å fortsette konfigurasjonsveiviseren]]> - Passordet til standardbrukeren må endres!]]> - Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> - Passordet til standardbrukeren har blitt forandret etter installasjonen!

Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> - Passordet er blitt endret! - Få en god start med våre introduksjonsvideoer - Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. - Ikke installert. - Berørte filer og mapper - Mer informasjon om å sette opp rettigheter for Umbraco her - Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper - Rettighetene er nesten perfekt satt opp!

Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
- Hvordan løse problemet - Klikk her for å lese tekstversjonen - innføringsvideo om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]> - Rettighetsinnstillingene kan være et problem!

Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
- Rettighetsinstillingene er ikke klargjort for Umbraco!

For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
- Rettighetsinnstillingene er perfekt!

Du er klar for å kjøre Umbraco og installere pakker!]]>
- Løser mappeproblem - Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper - Konfigurerer mappetillatelser - - Jeg ønsker å starte fra bunnen. - lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]> - Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå? - Runway er installert - Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]> - Bare anbefalt for erfarne brukere - Jeg vil starte med en enkel webside - "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider.

Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler.
Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri.
]]>
- Hva er Runway - Steg 1/5 Godta lisens - Steg 2/5 Database konfigurasjon - Steg 3/5: Valider filrettigheter - Steg 4/5: Skjekk Umbraco sikkerheten - Steg 5/5: Umbraco er klar for deg til å starte! - Tusen takk for at du valgte Umbraco! - Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]> - Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]> - Umbraco %0% er installert og klar til bruk - web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]> - starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor.
Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
- Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]> - Tilkobling til databasen mislyktes. - Umbraco Versjon 3 - Umbraco Versjon 4 - Se - Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.

Trykk "neste" for å starte veiviseren.]]>
- - - Språkkode - Språk - - - Du har vært inaktiv og vil logges ut automatisk om - Forny innlogging for å lagre - - - Da er det søndag! - Smil, det er mandag! - Hurra, det er tirsdag! - For en herlig onsdag! - Gledelig torsdag! - Endelig fredag! - Gledelig lørdag - Logg på nedenfor - Logg på med - Din sesjon er utløpt - © 2001 - %0%
umbraco.com

]]>
- - - Skrivebord - Seksjoner - Innhold - - - Velg side over... - %0% er nå kopiert til %1% - Kopier til - %0% er nå flyttet til %1% - Flytt til - har blitt valgt som rot til ditt nye innhold, klikk 'ok' nedenfor. - Ingen node er valgt, vennligst velg en node i listen over før du klikker 'fortsett' - Gjeldende nodes type tillates ikke under valgt node - Gjeldende node kan ikke legges under en underordnet node - Denne noden kan ikke ligge på rotnivå - Handlingen tillates ikke. Du mangler tilgang til en eller flere underordnede noder. - Relater kopierte elementer til original(e) - - - Rediger dine varsler for %0% - - Hei %0%

- -

Dette er en automatisk mail for å informere om at handlingen '%1%' - er blitt utført på siden '%2%' - av brukeren '%3%' -

- -

-

Rettelser:

- - %6% -
-

- - - -

Ha en fin dag!

- Vennlig hilsen Umbraco roboten -

]]>
- [%0%] Varsling om %1% utført på %2% - Varslinger - - - Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]> - Utvikler - Demonstrasjon - Dokumentasjon - Metadata - Pakkenavn - Pakken inneholder ingen elementer -
Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
- Ingen oppdateringer tilgjengelig - Alternativer for pakke - Lesmeg for pakke - Pakkebrønn - Bekreft avinstallering - Pakken ble avinstallert - Pakken ble vellykket avinstallert - Avinstaller pakke - Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]> - Last ned oppdatering fra pakkeregisteret - Oppgrader pakke - Oppgraderingsinstrukser - Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen. - Pakkeversjon - Pakkeversjonshistorie - Se pakkens nettsted - - - Lim inn med full formattering (Anbefales ikke) - Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside. - Lim inn som ren tekst, dvs. fjern al formattering - Lim inn og fjern uegnet formatering (anbefalt) - - - Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden - ved å bruke Umbraco's medlems-grupper]]> - Du må opprette en medlemsgruppe før du kan bruke rollebasert autentikasjon. - Feilside - Brukt når personer logger på, men ikke har tilgang - Hvordan vil du beskytte siden din? - %0% er nå beskyttet - Beskyttelse fjernet fra %0% - Innloggingsside - Velg siden som har loginformularet - Fjern beskyttelse - Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging. - Velg rollene som har tilgang til denne siden - Sett brukernavn og passord for denne siden - Enkelt: Beskytt ved hjelp av brukernavn og passord - Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord - - - %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato. - %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering. - %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen. - %0% kan ikke publiseres fordi en overordnet side ikke er publisert. - Inkluder upubliserte undersider - Publiserer - vennligst vent... - %0% av %1% sider har blitt publisert... - %0% er nå publisert - %0% og alle undersider er nå publisert - Publiser alle undersider - ok for å publisere %0% og dermed gjøre innholdet synlig for alle.

Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
- - - Du har ikke konfigurert noen godkjente farger - - - skriv inn ekstern lenke - velg en intern side - Tittel - Lenke - Åpne i nytt vindu - Skriv inn en tekst - Skriv inn en lenke - - - Nullstill - - - Gjeldende versjon - Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]> - Dokumentet er tilbakeført til en tidligere versjon - Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig. - Tilbakefør til - Velg versjon - Vis - - - Rediger scriptfilen - - - Concierge - Innhold - Courier - Utvikler - Umbraco konfigurasjonsveiviser - Mediaarkiv - Medlemmer - Nyhetsbrev - Innstillinger - Statistikk - Oversettelse - Brukere - Hjelp - Skjemaer - Analytics - - - gå til - Hjelpeemner for - Videokapitler for - De beste Umbraco opplæringsvideoer - - - Standardmal - Ordboksnøkkel - For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet) - Ny tittel på arkfane - Nodetype - Type - Stilark - Script - Stilark-egenskap - Arkfane - Tittel på arkfane - Arkfaner - Hovedinnholdstype aktivert - Denne dokumenttypen bruker - som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv. - Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap. - Hovedinnholdstype - Opprett tilhørende mal - - - Sort order - Creation date - Sortering ferdig. - Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. - - - - En feil oppsto - Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen - Avbrutt - Handlingen ble avbrutt av et tredjepartstillegg - Publisering ble avbrutt av et tredjepartstillegg - Egenskaptypen finnes allerede - Egenskapstype opprettet - DataType: %1%]]> - Egenskapstype slettet - Innholdstype lagret - Du har opprettet en arkfane - Arkfane slettet - Arkfane med id: %0% slettet - Stilarket ble ikke lagret - Stilarket ble lagret - Stilark lagret uten feil - Datatype lagret - Ordbokelement lagret - Publiseringen feilet fordi den overliggende siden ikke er publisert - Innhold publisert - og er nå synlig for besøkende - Innhold lagret - Husk å publisere for å gjøre endringene synlig for besøkende - Sendt for godkjenning - Endringer har blitt sendt til godkjenning - Media lagret - Media lagret uten feil - Medlem lagret - Stilarksegenskap lagret - Stilark lagret - Mal lagret - Feil ved lagring av bruker (sjekk loggen) - Bruker lagret - Brukertypen lagret - Filen ble ikke lagret - Filen kunne ikke lagres. Vennligst sjekk filrettigheter - Filen ble lagret - Filen ble lagret uten feil - Språk lagret - Python-skriptet ble ikke lagret - Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil - Python-skriptet er lagret! - Ingen feil i python-skriptet! - Malen ble ikke lagret - Vennligst forviss deg om at du ikke har to maler med samme alias - Malen ble lagret - Malen ble lagret uten feil! - Innhold avpublisert - Delmal lagret - Delmal lagret uten feil - Delmal ble ikke lagret! - En feil oppsto ved lagring av delmal - Script visning lagret - Script visning lagret uten feil! - Script visning ikke lagret - En feil oppsto under lagring av filen. - En feil oppsto under lagring av filen. - - - Bruk CSS syntaks f.eks: h1, .redHeader, .blueText - Rediger stilark - Rediger egenskap for stilark - Navn for å identifisere stilarksegenskapen i rik-tekst editoren - Forhåndsvis - Stiler - - - Rediger mal - Sett inn innholdsområde - Sett inn plassholder for innholdsområde - Sett inn ordbokselement - Sett inn makro - Sett inn Umbraco sidefelt - Hovedmal - Hurtigguide til Umbraco sine maltagger - Mal - - - Rich Text Editor - Image - Macro - Embed - Headline - Quote - Sett inn element - Velg layout - Legg til rad - Legg til innhold - Slipp innhold - Raden har tilpasset design - - Innholdstypen er ikke tillatt her - Innholdstypen er tillatt her - - Klikk for å bygge inn - Klikk for å sette inn et bilde - Bildetekst... - Skriv her... - - Rutenettoppsett - Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ett eller to - Legg til rutenettoppsett - Juster oppsettet ved å konfigurere kolonnebredder og legge til ytterligere seksjoner - Radkonfigurasjoner - Rader er forhåndsdefinerte celler arrangert vannrett - Legg til radkonfigurasjon - Juster raden ved å sette cellebredder og legge til flere celler - - Kolonner - Totalt antall kolonner i rutenettet - - Innstillinger - Konfigurer hvilke innstillinger brukeren kan endre - - Stiler - Konfigurer hvilke stiler redaktørene kan endre - - Innstillingene lagres kun når konfigurasjonen er gyldig - - Tillatt alle editorer - Tillat alle radkonfigurasjoner - Bruk som standard - Velg ekstra - Velg standard - er lagt til - - - Alternativt felt - Alternativ tekst - Store/små bokstaver - Encoding - Felt som skal settes inn - Konverter linjeskift - Erstatter et linjeskift med htmltaggen <br> - Egendefinerte felt - Ja, kun dato - Formatter som dato - HTML koding - Formater spesialtegn med tilsvarende HTML-tegn. - Denne teksten vil settes inn etter verdien av feltet - Denne teksten vil settes inn før verdien av feltet - Små bokstaver - Ingen - Sett inn etter felt - Sett inn før felt - Rekursivt - Standardfelter - Store bokstaver - URL koding - Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres - Denne teksten vil benyttes dersom feltene over er tomme - Dette feltet vil benyttes dersom feltet over er tomt - Ja, med klokkeslett. Dato/tid separator: - - - Oppgaver satt til deg - som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML".
For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
- Lukk oppgave - Oversettelses detaljer - Last ned all oversettelsesoppgaver som XML - Last ned XML - Last ned XML DTD - Felt - Inkluder undersider - - [%0%] Oversettingsoppgave for %1% - Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting - Oppgaver opprettet av deg - opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]> - Siden '%0%' har blitt sendt til oversetting - Send til oversetting - Tildelt av - Oppgave åpnet - Antall ord - Oversett til - Oversetting fullført. - Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene. - Oversetting mislykkes, XML filen kan være korrupt - Alternativer for oversetting - Oversetter - Last opp XML med oversettelse - - - Hurtigbufferleser - Papirkurv - Opprettede pakker - Datatyper - Ordbok - Installerte pakker - Installer utseende - Installer startpakke - Språk - Installer lokal pakke - Makroer - Mediatyper - Medlemmer - Medlemsgrupper - Roller - Medlemstyper - Dokumenttyper - Pakker - Pakker - Python Filer - Installer fra pakkeregister - Installer Runway - Runway moduler - Skriptfiler - Skript - Stiler - Maler - Analytics - Brukertillatelser - Brukertyper typer - Brukere - - - - - - Ny oppdatering er klar - %0% er klar, klikk her for å laste ned - Ingen forbindelse til server - Kunne ikke sjekke etter ny oppdatering. Se trace for mere info. - - - Administrator - Kategorifelt - Bytt passord - Nytt passord - Bekreft nytt passord - Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen. - Innholdskanal - Beskrivelsesfelt - Deaktiver bruker - Dokumenttype - Redaktør - Utdragsfelt - Språk - Brukernavn - Øverste nivå i Media - Moduler - Deaktiver tilgang til Umbraco - Passord - Nullstill passord - Passordet er endret - Bekreft nytt passord - Nytt passord - Nytt passord kan ikke være blankt - Gjeldende passord - Feil passord - Nytt og bekreftet passord må være like - Nytt og bekreftet passord må være like - Overskriv tillatelser på undernoder - Du redigerer for øyeblikket tillatelser for sidene: - Velg sider for å redigere deres tillatelser - Søk i alle undersider - Startnode - Navn - Brukertillatelser - Forfatter - Oversetter - Endre - Din profil - Din historikk - Sesjonen utløper om - -
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml deleted file mode 100644 index 93f6f5a10f..0000000000 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ /dev/null @@ -1,1346 +0,0 @@ - - - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - 管理主機名稱 - 跟蹤審計 - 流覽節點 - 改變文檔類型 - 複製 - 創建 - 創建擴展包 - 刪除 - 禁用 - 清空回收站 - 匯出文檔類型 - 導入文檔類型 - 導入擴展包 - 即時編輯模式 - 退出 - 移動 - 提醒 - 公眾存取權限 - 發佈 - 取消發佈 - 重新載入節點 - 重新發佈整站 - 回復 - 許可權 - 回滾 - 提交至發佈者 - 發送給翻譯 - 排序 - 提交至發佈者 - 翻譯 - 更新 - 預設值 - - - 禁止訪問 - 添加功能變數名稱 - 移除 - 錯誤的節點 - 功能變數名稱錯誤 - 功能變數名稱重複 - 語言 - 功能變數名稱 - 新功能變數名稱 '%0%' 已創建 - 功能變數名稱 '%0%' 已刪除 - 功能變數名稱 '%0%' 已使用 - 功能變數名稱 '%0%' 已更新 - 編輯當前功能變數名稱 - - 繼承 - 語言 - 或從父節點繼承文化設定。
- 也會改變目前節點設定,除非下方網域有其他項目。]]>
- 功能變數名稱 - - - 查看 - - - 清除選擇 - 選擇 - 選擇目前資料夾 - 做別的事情 - 粗體 - 取消段落縮進 - 插入表單字段 - 插入圖片標題 - 編輯Html - 段落縮進 - 斜體 - 居中 - 左對齊 - 右對齊 - 插入連結 - 插入本地連結(錨點) - 圓點列表 - 數字清單 - 插入巨集 - 插入圖片 - 編輯關聯 - 回到清單 - 保存 - 保存並發佈 - 保存並提交審核 - 保存清單檢視 - 預覽 - 因未設置範本無法預覽 - 選擇樣式 - 顯示樣式 - 插入表格 - 產生模組 - - - 要更改所選節點的文檔類型,先在列表中選擇合適的文檔類型。 - 然後設置當前文檔類型到新文檔類型的各欄位間的對應映射關係並保存。 - 內容已被重新發佈 - 當前屬性 - 當前類型 - 不能改變文檔類型,因為沒有可替代的類型。 - 文檔類型已更改 - 要映射的欄位 - 映射欄位 - 新範本 - 新類型 - - 內容 - 選擇新的文檔類型 - 選中文檔的類型已被成功更改為[new type],以下欄位被映射: - - 不能完成欄位映射,因為存在一個欄位映射至多欄位的問題。 - 僅顯示可作為替代的文檔類型。 - - - 已發表 - 關於本頁 - 別名 - (圖片的替代文本) - 替代連結 - 點擊編輯 - 創建者 - 創建者 - 更新者 - 創建時間 - 此文件創建的日期時間 - 文檔類型 - 編輯 - 過期於 - 該項發佈之後有更改 - 該項沒有發佈 - 最近發佈 - 沒有可供顯示的項目 - 此列表中沒有可供顯示的項目 - 媒體類型 - 媒體連結位址 - 會員組 - 角色 - 會員類型 - 沒有選擇時間 - 頁標題 - 屬性 - 該文檔不可見,因為其上級 '%0%' 未發佈。 - 糟糕:該文檔已發佈,但是沒有更新至緩存(內部錯誤) - 糟糕:沒辦法連結到此網址(內部錯誤-請參見記錄) - 糟糕:此文件已經發表,但是網址和其他內容相衝 %0% - 發佈 - 發佈狀態 - 發佈於 - 取消發表於 - 清空時間 - 排序完成 - 拖拽項目或按一下列頭即可排序,可以按住Shift多選。 - 統計 - 標題(可選) - 其他說明文字(可選) - 類型 - 取消發佈 - 最近編輯 - 本文件修改時間 - 移除文件 - 連結到文檔 - 會員組成員 - 非會員組成員 - 子項目 - 目標 - 預計發表的時間(伺服器端) - 這是什麼意思?]]> - - - 點選以便上傳 - 拖曳檔案至此... - 媒體連結 - 或按這裡選擇檔案 - 只允許檔案類型為 - 檔案大小上限為 - - - 新增一位會員 - 所有會員 - - - 您想在哪裡創建 %0% - 創建在 - 選擇類型和標題 - "文檔類型"處變更。]]> - "媒體類型"處變更。]]> - 文檔類型沒有相關範本 - 沒有資料夾 - 新資料類別 - - - 流覽您的網站 - - 隱藏 - 如果Umbraco沒有打開,您可能需要允許彈出式視窗。 - 已經在新視窗中打開 - 重啟 - 訪問 - 歡迎 - - - 留下 - 放棄變更 - 您有未存檔的變更 - 您確定要離開本頁? - 您有未存檔的變更 - - - 完成 - 刪除 %0% 個項目 - 刪除 %0% 個項目 - 刪除 %1% 個中的 %0% 個項目 - 刪除 %1% 個中的 %0% 個項目 - 已發佈 %0% 個項目 - 已發佈 %0% 個項目 - 已發佈 %1% 個中的 %0% 個項目 - 已發佈 %1% 個中的 %0% 個項目 - 取消發佈 %0% 個項目 - 取消發佈 %0% 個項目 - 取消發佈 %1 個中的 %0% 個項目 - 取消發佈 %1 個中的 %0% 個項目 - 移動 %0% 個項目 - 移動 %0% 個項目 - 移動 %1 個中的 %0% 個項目 - 移動 %1 個中的 %0% 個項目 - 複製 %0% 個項目 - 複製 %0% 個項目 - 複製 %1 個中的 %0% 個項目 - 複製 %1 個中的 %0% 個項目 - - - 錨點名稱 - 管理主機名稱 - 關閉窗口 - 您確定要刪除嗎 - 您確定要禁用嗎 - 按一下此框確定刪除%0%項 - 您確定嗎? - 您確定嗎? - 剪切 - 編輯字典項 - 編輯語言 - 插入本地連結 - 插入字元 - 插入圖片標題 - 插入圖片 - 插入連結 - 插入巨集 - 插入表格 - 最近編輯 - 連結 - 內部連結: - 本地連結請用“#”號開頭 - 在新視窗中打開? - 巨集設置 - 本巨集沒有包含您可以編輯的屬性 - 粘貼 - 編輯許可權 - 正在清空回收站,請不要關閉窗口。 - 回收站已清空 - 從回收站刪除的項目將不可恢復 - regexlib.com的網站服務目前出現些狀況,而我們無能為力。我們對此不便感到十分抱歉。]]> - 查找規則運算式來驗證輸入,如: 'email、'zip-code'、'url'。 - 移除巨集 - 必填項目 - 網站已重建索引 - 網站緩存已刷新,所有已發佈的內容更新生效。 - 網站緩存將會刷新,所有已發佈的內容將會更新。 - 表格列數 - 表格行數 - 設定預留位置代碼 以便您要在子範本中插入內容到本範本時,填入此代碼到 <asp:content /> 裡面。]]> - 選擇預留位置代碼 於此清單中。您只能選擇目前父範本中的代碼。]]> - 點擊圖片查看完整大小 - 拾取項 - 查看緩存項 - 新增資料夾... - 與原本相關 - 最友善的社群 - 頁面連結 - 打開此連結文檔至新視窗或標籤頁 - 打開此連結文檔至全新視窗 - 打開此連結文檔在原本視窗中 - 媒體連結 - 選擇媒體 - 選擇圖示 - 選擇項目 - 選擇連結 - 選擇巨集 - 選擇內容 - 選擇會員 - 選擇會員群組 - 沒有找到任何圖示 - 本巨集沒有需要參數 - 外部登入提供者 - 例外細節 - 詳細記錄 - 內部例外 - 連結您的 - 取消連結您的 - 帳戶 - 選擇編輯器 - - - %0%' 編輯不同語言版本,
您可以在左方選單「語言」中增添新的語言 - ]]>
- 語言名稱 - - - 輸入您的使用者名稱 - 輸入您的密碼 - 確認您的密碼 - 命名此 %0%... - 輸入一個名稱 - 標籤... - 輸入一段描述... - 搜尋請輸入... - 過濾請輸入... - 增加標籤(每個標籤後請按輸入鍵)... - 輸入您的電子郵件 - - - 允許放置於根節點 - 只有勾選「允許放置於根節點」的內容種類可以放在內容樹或媒體樹的最頂端 - 允許子項節點類型 - 文檔種類集合 - 創建 - 刪除選項卡 - 描述 - 新建選項卡 - 選項卡 - 縮略圖 - 允許清單檢視 - 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 - 目前清單檢視 - 作用中的清單檢視資料類別 - 新增自訂清單檢視 - 移除自訂清單檢視 - - - 添加預設值 - 資料庫資料類型 - 資料類型唯一標識 - 渲染控制項 - 按鈕 - 允許高級設置 - 允許快顯功能表 - 插入圖片預設最大 - 關聯的樣式表 - 顯示標籤 - 寬和高 - - - 資料已保存,但是發佈前您需要修正一些錯誤: - 當前成員提供程式不支援修改密碼(EnablePasswordRetrieval的值應該為true) - %0% 已存在 - 發現錯誤: - 發現錯誤: - 密碼最少%0%位元,且至少包含%1%位元非字母數位記號 - %0% 必須是整數 - %1% 中的 %0% 欄位是必填項 - %0% 是必填項 - %1% 中的 %0% 格式不正確 - %0% 格式不正確 - - - 收到伺服器傳來的錯誤 - 該檔案類型已被管理員禁用 - 注意,儘管配置中允許CodeMirror,但是它在IE上不夠穩定,所以無法在IE運行。 - 請為新的屬性類型填寫名稱和別名! - 許可權有問題,訪問指定文檔或資料夾失敗! - 讀取片段視圖腳本錯誤(檔案:%0%) - 讀取使用者控制項 %0% 錯誤 - 讀取使用者控制項 %0% 錯誤(組件:%0%,類別:%1%) - 讀取巨集引擎腳本錯誤(檔案:%0%) - 請輸入標題 - 請選擇類型 - 圖片尺寸大於原始尺寸不會提高圖片品質,您確定要把圖片尺寸變大嗎? - python腳本錯誤 - python腳本未保存,因為包含錯誤。 - 預設打開頁面不存在,請聯繫管理員 - 請先選擇內容,再設置樣式。 - 沒有可用的樣式 - 請把游標放在您要合併的兩個儲存格中的左邊儲存格 - 非合併儲存格不能分離。 - 這是此屬性所使用的資料類別設定錯誤,請檢查資料類別 - - - 關於 - 操作 - 操作 - 添加 - 別名 - 所有 - 您確定嗎? - 回去 - 邊框 - - 取消 - 儲存格邊距 - 選擇 - 關閉 - 關閉窗口 - 備註 - 確認 - 強制屬性 - 繼續 - 複製 - 創建 - 資料庫 - 時間 - 默認 - 刪除 - 已刪除 - 正在刪除… - 設計 - 規格 - - 下載 - 編輯 - 已編輯 - 元素 - 郵箱 - 錯誤 - 查找文檔 - - 幫助 - 圖示 - 導入 - 內邊距 - 插入 - 安裝 - 不合格 - 對齊 - 語言 - 佈局 - 載入中 - 鎖定 - 登入 - 退出 - 登出 - 巨集 - 必要 - 移動 - 更多 - 名稱 - 新的 - 下一步 - - 屬於 - 確定 - 打開 - - 密碼 - 路徑 - 預留位置代碼 - 請稍候… - 上一步 - 屬性 - 接收資料郵箱 - 回收站 - 保持狀態中 - 重命名 - 更新 - 必要 - 重試 - 許可權 - 搜索 - 伺服器 - 顯示 - 在發送時預覽 - 大小 - 排序 - 送出 - 類型 - 輸入內容開始搜尋… - - 更新 - 更新 - 上傳 - 連結位址 - 用戶 - 用戶名 - - 查看 - 歡迎… - - - 資料夾 - 搜尋結果 - 重新排列 - 我已經完成排列 - 預覽 - 更改密碼 - - 清單檢視 - 存檔中... - 目前 - 內嵌 - 選取的 - - - - - - - - - - - 增加標籤頁 - 增加屬性 - 增加編輯器 - 增加範本 - 增加子節點 - 增加子項目 - 編輯資料類別 - 瀏覽區塊 - 捷徑 - 顯示捷徑 - 開關清單檢視 - 開關是否允許為根項目 - - - 背景色 - 粗體 - 前景色 - 字體 - 文本 - - - 頁面 - - - 無法連接到資料庫。 - 無法保存web.config檔,請手工修改。 - 發現資料庫 - 資料庫配置 - 安裝 按鈕來安裝Umbraco資料庫 %0% - ]]> - 下一步繼續。]]> - 沒有找到資料庫!請確認檔案"web.config"中的字串"connection string"是否正確。

-

請編輯檔案"web.config" (例如使用Visual Studio或您喜歡的編輯器),移動到檔案底部,並在名稱為"UmbracoDbDSN"的字串中設定資料庫連結資訊,並存檔。

-

- 點選重試按鈕當上述步驟完成。
- - 在此查詢更多編輯web.config的資訊。

]]>
- - 若需要時,請聯繫您的網路公司。如果您在本地機器或伺服器安裝的話,您也許需要聯絡系統管理者。]]> - - 點選升級按鈕來升級Umbraco資料庫 %0%

-

- 請別擔心 - 不會刪除任何資料而且馬上就會繼續運作! -

- ]]>
- 點選下一步繼續。]]> - 下一步繼續設定精靈。]]> - 預設使用者的密碼必須更改!]]> - 預設使用者已經被暫停或沒有Umbraco的使用權!

不需更多的操作步驟。點選下一步繼續。]]> - 安裝後預設使用者的密碼已經成功修改!

不需更多的操作步驟。點選下一步繼續。]]> - 密碼已更改 - 作為入門者,從視頻教程開始吧! - 點擊下一步 (或在Web.config中自行修改UmbracoConfigurationStatus),意味著您接受上述授權合約。 - 安裝失敗。 - 受影響的檔和資料夾 - 此處查看更多資訊 - 您需要對以下檔和資料夾授於ASP.NET用戶修改許可權 - 您的權限設定幾近完美!

- 您可以正常執行Umbraco沒有任何問題,只差您將沒有辦法安裝那些建議需要全部許可權的插件。]]>
- 如何解決 - 點擊閱讀文字版 - 影片教學來瞭解如何設定Umbraco的資料夾權限或閱讀文字版本。]]> - 您的權限可能有點小問題! -

- 您可以正常執行Umbraco沒有任何問題,然而您將無法新增資料夾或安裝那些可以讓Umbraco發揮全力的插件。]]>
- 您的權限設定尚未未完成! -

- 您需要更新權限設定才能執行Umbraco。]]>
- 您的權限設定完美無瑕!

- 您已經準備好執行Umbraco和安裝插件!]]>
- 解決資料夾問題 - 點此查看ASP.NET和創建資料夾的問題解決方案 - 設置資料夾許可權 - - 我要從頭開始 - 學習該怎麼做) - 您晚點仍可以選擇安裝Runway,請至開發者區域選擇安裝。 - ]]> - 您剛剛安裝了一個乾淨的系統,要繼續嗎? - “Runway”已安裝 - - 這是我們的模組推薦清單,選取您想要安裝的項目,或者至 查詢完整清單。 - ]]> - 僅推薦高級用戶使用 - 給我一個簡單的網站 - - "Runway"是一個提供基本檔案類別和範本的簡單網站。安裝程式會自動幫您設定Runway, - 但你仍可輕易編輯,擴充或移除它。它並非必要項目而且您可以在沒它的情況下完美執行Umbraco。然而, - Runway提供一個輕鬆簡便但基於寶貴經驗的平台讓您可以更快開始。 - 如果您安裝Runway,您還可以選擇名為「Runway模組」的基本區塊來加強Runway頁面。 -

- - 內含於Runway: 首頁,準備開始頁面,模組安裝頁面。
- 可選模組: 上方瀏覽列,網站地圖,聯絡,藝廊。 -
- ]]>
- “Runway”是什麼? - 步驟 1/5:接受授權合約 - 步驟 2/5:資料庫配置 - 步驟 3/5:文件許可權驗證 - 步驟 4/5:系統安全性 - 步驟 5/5:一切就緒,可以開始使用系統。 - 感謝選擇我們的產品 - 參觀您的新網站 -您剛安裝好Runway,何不瞧瞧它的模樣。]]> - 更多的幫忙與資訊 -從我們獲獎的社群得到幫助,瀏覽文件,或觀看免費影片來瞭解如何輕鬆架設網站,如何使用插件,和瞭解Umbraco項目名稱的快速上手指引。]]> - 系統 %0% 安裝完畢 - /web.config 檔案並且更新AppSetting中的字串UmbracoConfigurationStatus 內容為 '%0%'。]]> - 快速開始指引。
如果您是Umbraco的新成員, -您可以在其中找到相當多的資源。]]>
- 啟動Umbraco -想要管理您的網站時,只需開啟Umbraco後台便可增加內容,更新範本和樣式表,或增添新功能。]]> - 無法連接到資料庫。 - 系統版本 3 - 系統版本 4 - 觀看 -
- 點選"下一步"來啟動精靈。]]>
- - - 語言代碼 - 語言名稱 - - - 使用者在空閒狀態下將會自動登出 - 已更新,繼續工作。 - - - 超級星期天快樂 - 瘋狂星期一快樂 - 熱鬧星期二快樂 - 美妙星期三快樂 - 悅耳星期四快樂 - 時髦星期五快樂 - 喵喵星期六快樂 - 下方登入 - 登入使用 - 連線時間過了 - © 2001 - %0%
Umbraco.com

]]>
- 忘記密碼? - 一封內有重設密碼連結的電子郵件已經寄出給您 - 一封內有重設密碼連結的電子郵件已經寄到此信箱 - 回到登入畫面 - 請輸入新密碼 - 您的密碼已經更新 - 您點選的連結是無效或過期的 - Umbraco:重設密碼 - 您登入到後台的使用者名稱是:%0%

點選這裡來重設您的密碼或將此連結複製/貼上到您的瀏覽器:

%1%

]]>
- - - 儀錶板 - 區域 - 內容 - - - 選擇上面的頁面… - %0% 被複製到 %1% - 將 %0% 複製到 - %0% 已被移動到 %1% - 將 %0% 移動到 - 作為內容的根結點,點“確定”。 - 尚未選擇節點,請選擇一個節點點擊“確定”。 - 類型不符不允許選擇 - 該項不能移到其子項 - 當前節點不能建在根節點下 - 您在子項的許可權不夠,不允許該操作。 - 複本和原本建立關聯 - - - 為 %0% 編寫通知 - - 哈嘍 %0%

- -

這是一封自動產生的信件來通知您 %1% 工作 - 已經在頁面 %2% 上由使用者 %3% 執行完成 -

- -

-

更新摘要:

- - %6% -
-

- - - -

祝您有美好的一天!

- Umbraco機器人 謹上 -

]]>
- 在 %2%,[%0%] 關於 %1% 的通告已執行。 - 通知 - - - - 按鈕並點選該檔案。Umbraco擴展包通常有「.zip」的副檔名。 - ]]> - 作者 - 演示 - 文檔 - 中繼資料 - 名稱 - 擴展包不含任何項 -
- 您可以點選下方「移除擴展包」來安全地移除此項目。]]>
- 無可用更新 - 選項 - 說明 - 程式庫 - 確認卸載 - 已卸載 - 擴展包卸載成功 - 卸載 - - 注意: 任何文檔,媒體或需要這些項目才能運作的物件將會停止運作,並可能使得系統不穩定, - 請小心移除。若有疑慮,請聯絡擴展包作者。]]> - 從程式庫下載更新 - 更新擴展包 - 更新說明 - 擴展包有可用的更新,您可以從程式庫網站更新。 - 版本 - 版本歷史 - 訪問擴展包網站 - 擴展包已安裝 - 這個擴展包無法安裝,它需要Umbraco至少是版本 %0% - 移除中... - 下載中... - 匯入中... - 安裝中... - 重新啟動中,請稍後... - 都好了,您的瀏覽器將重新整理,請稍待... - - - 帶格式粘貼(不推薦) - 您所粘貼的文本含有特殊字元或格式,Umbraco將清除以適應網頁。 - 無格式粘貼 - 粘貼並移除格式(推薦) - - - 基於角色的保護 - 請使用Umbraco的會員群組。]]> - 使用基於角色的授權需要首先建立會員組。 - 錯誤頁 - 當用戶登錄後訪問沒有許可權的頁時顯示該頁 - 選擇限制訪問此頁的方式 - %0% 現在處於受保護狀態 - %0% 的保護被取消 - 登錄頁 - 選擇公開的登錄入口 - 取消保護 - 選擇一個包含登錄表單和提示資訊的頁 - 選擇訪問該頁的角色類型 - 為此頁設置帳號和密碼 - 單用戶保護 - 如果您只希望提供一個用戶名和密碼就能訪問 - - - - - - - - 包含未發佈的子項 - 正在發佈,請稍候… - %0% 中的 %1% 頁面已發佈… - %0% 已發佈 - %0% 及其子項已發佈 - 發佈 %0% 及其子項 - 發佈按鈕來將%0%的內容設定為公開。

- 您可以同時發佈本頁以及其子項目若您點選下面的包含子頁。 - ]]>
- - - 您尚未設定任何許可顏色 - - - 輸入外部連結 - 選擇內部連結 - 標題 - 連結 - 新視窗 - 輸入新標題 - 輸入連結 - - - 重設 - - - 當前版本 - 紅色 文字將不會顯示於所選版本,而綠色表示增加部分。]]> - 文檔已回滾 - 這顯示所選版本的HTML格式,如果您想要比較兩版本的差異,請使用比較檢視 - 回滾至 - 選擇版本 - 查看 - - - 編輯腳本 - - - Concierge - 內容 - Courier - 開發 - 設定精靈 - 媒體 - 會員 - 消息 - 設置 - 統計 - 翻譯 - 用戶 - 說明 - 表單 - 統計 - - - 移至 - 說明主題為 - 影片主題為 - 最好的Umbraco影片教學 - - - 預設範本 - 字典鍵 - 要導入文檔類型,請點擊“流覽”按鈕,再點擊“導入”,然後在您電腦上查找 ".udt"檔導入(下一頁中需要您再次確認) - 新建選項卡標題 - 節點類型 - 類型 - 樣式表 - 腳本 - 樣式表屬性 - 選項卡 - 選項卡標題 - 選項卡 - 主控文件類型啟動 - 該文檔類型使用 - 作為主控文件類型. 主控文件類型的標籤只能在主控文件類型裡修改。 - 沒有欄位設置在該標籤頁 - 主文檔類別 - 新增對應範本 - 增加圖示 - - - 排列順序 - 增添時間 - 排序完成。 - 上下拖拽項目或按一下列頭進行排序 - - - - 驗證 - 驗證錯誤一定要修正才能儲存項目 - 失敗 - 使用者權限不足,無法完成操作 - 已取消 - 操作被協力廠商外掛程式取消 - 發佈被協力廠商外掛程式取消 - 屬性類型已存在 - 屬性類型已創建 - 資料類別:%1%]]> - 屬性類型已刪除 - 內容類別型已保存 - 選項卡已創建 - 選項卡已刪除 - id為%0%的選項卡已刪除 - 樣式表未保存 - 樣式表已保存 - 樣式表保存,無錯誤。 - 資料類型已保存 - 字典項已保存 - 因為上級頁面未發佈導致發佈失敗! - 內容已發佈 - 公眾可見 - 內容已保存 - 請發佈以使更改生效 - 提交審核 - 更改已提交審核 - 媒體已保存 - 媒體已保存 - 會員已保存 - 樣式表屬性已保存 - 樣式表已保存 - 範本已保存 - 保存使用者出錯(請查看日誌) - 用戶已保存 - 用戶類型已保存 - 檔未保存 - 檔無法保存,請檢查許可權。 - 檔保存 - 檔保存,無錯誤。 - 語言已保存 - 媒體類別已儲存 - 會員類別已儲存 - Python腳本未保存 - Python腳本因為錯誤未能保存 - Python已保存 - Python腳本無錯誤 - 範本未保存 - 範本別名相同 - 範本已保存 - 範本保存,無錯誤。 - 內容已取消發佈 - 片段視圖已保存 - 片段視圖保存,無錯誤。 - 片段視圖未保存 - 片段視圖因為錯誤未能保存 - 腳本視圖已儲存 - 腳本視圖已儲存,沒有任何錯誤! - 腳本視圖未儲存 - 儲存檔案時發生錯誤 - 儲存檔案時發生錯誤 - - - 使用CSS語法,如:h1、.redHeader、.blueTex。 - 編輯樣式表 - 編輯樣式屬性 - 編輯器中的樣式屬性名 - 預覽 - 樣式 - - - 編輯範本 - 插入內容區 - 插入內容預留位置 - 插入字典項 - 插入巨集 - 插入頁欄位 - 母版 - 範本標籤快速指南 - 範本 - - - Rich Text Editor - Image - Macro - Embed - Headline - Quote - 選擇內容類別 - 選擇排列方式 - 新增一行 - 新增內容 - 放棄內容 - 設定已儲存 - 此處不允許有內容 - 此處允許有內容 - 點選來內嵌 - 點選來插入圖片 - 圖片標題... - 在此填寫... - 網格排列方式 - 排列是指網格編輯器的整體工作區域,通常您只需要一種或兩種排列方式 - 增加網格排列方式 - 藉由設定列寬以及增加新的區域來調整排列方式 - 行設定 - 行是預先水平排列的格子 - 增加行設定 - 藉由設定小格寬度和增添小格來調整此行 - - 網格排列方式的列總數 - 設定 - 調整設定編輯器可以改變的項目 - 樣式 - 調整樣式編輯器可以改變的項目 - 當JSON格式正確時設定才可以儲存 - 允許所有編輯器 - 允許所有行設定 - 定為預設 - 選擇額外 - 選擇預設 - 已增加 - - - 組合 - 您沒有增加任何選項卡 - 增加新的選項卡 - 增加另外的選項卡 - 繼承的表格 - 增加屬性 - 必要標籤 - 允許清單檢視 - 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 - 允許的範本 - 選擇哪些範本編輯器可以使用於此類別的內容 - 允許為根項目 - 允許編輯器新增此類別的內容為根項目 - 是的 - 允許此類別內容為根項目 - 允許子節點種類 - 允許某些特定種類能夠成為此種類內容的子項目 - 選擇子節點 - 從已存在的文檔類別中繼承選項卡以及屬性。新選項卡將被新增至目前文檔種類或合併至已存在同名的選項卡中。 - 此內容種類已經用於集合中,因此不能重複添加本身。 - 沒有可用於集合的內容種類。 - 可用的編輯器 - 重複使用 - 編輯器設定 - 設定 - 是,刪除 - 已移至下層 - 已複製至下層 - 選擇要移動的資料夾 - 選擇要複製的資料夾 - 至下方樹狀結構 - 所有文檔種類 - 所有文檔 - 所有媒體項目 - 使用此文檔種類的將被永久刪除,請確認您也想要將它們刪除。 - 使用此媒體種類的將被永久刪除,請確認您也想要將它們刪除。 - 使用此會員種類的將被永久刪除,請確認您也想要將它們刪除。 - 以及所有使用此種類的文件項目 - 以及所有使用此種類的媒體項目 - 以及所有使用此種類的會員項目 - 使用此編輯器將會套用新設定 - 會員可以編輯 - 顯示於會員資料 - - - 替代欄位 - 替代文本 - 大小寫 - 編碼 - 選取欄位 - 轉換分行符號 - 將換行符號取代成為HTML標籤 &lt;br&gt; - 自訂欄位 - 是,僅日期 - 格式化時間 - HTML編碼 - 將替換HTML中的特殊字元 - 將在欄位值後插入 - 將在欄位值前插入 - 小寫 - - 欄位後插入 - 欄位前插入 - 遞迴 - 標準欄位 - 大寫 - URL編碼 - 將格式化URL中的特殊字元 - 當上面欄位值為空時使用 - 該欄位僅在主欄位為空時使用 - 是,含時間,分隔符號為: - - - 標記為您的任務 - 指派給您。請按「翻譯詳情」或頁面名稱觀看包含回應的詳細檢視畫面。 - 您也可以將此頁面下載成為XML格式檔案,請按「下載XML」按鈕。
- 若想要關閉翻譯任務,請至細節頁面點選「結束」按鈕。 - ]]>
- 關閉任務 - 翻譯詳情 - 將翻譯任務下載為XML - 下載 XML - 下載 XML DTD - 欄位 - 包含子頁 - - [%0%]翻譯任務:%1% - 沒有翻譯員,請創建翻譯員角色的用戶。 - 您創建的任務 - 由您創建的頁面。要瀏覽包含回應的詳細檢視畫面, - 點選「詳情」或頁面名稱。您可以下載此頁面成為XML格式檔案,請點選「下載XML」連結。 - 若要關閉翻譯任務,請至詳情檢視並點選「關閉」按鈕。 - ]]> - 頁面'%0%'已經發送給翻譯 - 請選擇本內容應該被翻譯成的語言 - 發送頁面'%0%'以便翻譯 - 分配者 - 任務開啟 - 總字數 - 翻譯到 - 翻譯完成。 - 您可以流覽剛翻譯的頁面,如果原始頁存在,您將得到兩者的比較。 - 翻譯失敗,XML可能損壞了。 - 翻譯選項 - 翻譯員 - 上傳翻譯的xml - - - 緩存流覽 - 回收站 - 創建擴展包 - 資料類型 - 字典 - 已安裝的擴展包 - 安裝皮膚 - 安裝新手套件 - 語言 - 安裝本地擴展包 - 巨集 - 媒體類型 - 會員 - 會員組 - 角色 - 會員類型 - 文檔類型 - 相關類型 - 擴展包 - 擴展包 - Python文件 - 從線上程式庫安裝 - 安裝Runway - Runway模組 - Scripting文件 - 腳本 - 樣式表 - 範本 - 統計 - - - - - - 有可用更新 - %0%已就緒,點擊這裡下載 - 無到伺服器的連接 - 檢查更新失敗 - - - 管理員 - 分類欄位 - 更改密碼 - 更改密碼 - 確認新密碼 - 要改變密碼,請在框中輸入新密碼,然後按一下“更改密碼”。 - 內容頻道 - 描述欄位 - 禁用用戶 - 文檔類型 - 編輯 - 排除欄位 - 語言 - 登錄 - 默認打開媒體項 - 區域 - 禁用後臺管理介面 - 舊的密碼 - 密碼 - 重設密碼 - 您的密碼已更改! - 重輸密碼 - 輸入新密碼 - 新密碼不能為空! - 當前密碼 - 密碼錯誤 - 新密碼和重輸入的密碼不一致,請重試! - 重輸的密碼和原密碼不一致! - 替換子項許可權設置 - 您正在修改存取權限的頁面: - 選擇要修改許可權的頁 - 搜索子物件 - 預設打開內容項 - 用戶名 - 用戶許可權 - 撰稿人 - 翻譯者 - 改變 - 您的個人檔案 - 您的歷程記錄 - 連線到期於 - - - 驗證 - 以電子郵件驗證 - 以數字驗證 - 以網址驗證 - ...或輸入自訂驗證 - 必要欄位 - - - - 數值已設為推薦值:%0% - 在設定檔 %3% 中XPath %2% 的數值設為 %1% 。 - 在設定檔 %3% 中XPath %2% 的預期值設為 %1% ,但卻是 %0%。 - 在設定檔 %3% 中XPath %2% 的值為非預期值 %0%。 - - 自訂錯誤設定為 %0% - 自訂錯誤設定為 %0。建議在上線前改為 %1%。 - 自訂錯誤成功設定為 %0% - 巨集錯誤設為 %0% - 巨集錯誤設為 %0%,如此一來,當巨集有任何錯誤時會阻止某些或全部頁面正常載入。改正會將此設定 %1%。 - 巨集錯誤已設為 %0% - - 嘗試略過IIS自訂錯誤目前設為 %0%,而且您使用的IIS版本為 %1%。 - 嘗試略過IIS自訂錯誤目前設為 %0%,然而在您使用的IIS版本為 %2% 時,建議設定是 %1%。 - 嘗試略過IIS自訂錯誤已成功設為 %0%。 - - 檔案不存在:%0%。 - '%1%'中無法找到'%0%'。]]> - 有錯誤產生,請參閱下列錯誤的紀錄:%0%。 - 成員 - 所有XML:%0%,總共:%1%,不合格:%2% - 媒體 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 內容 - 所有XML:%0%,總共發佈:%1%,不合格:%2% - 憑證驗證錯誤:%0% - 網址探查錯誤:%0% - '%1%' - 您目前使用HTTPS瀏覽本站:%0% - 在您的web.config檔案中,appSetting的umbracoUseSSL是設為false。當您開始使用HTTPS時,應將其改為 true。 - 在您的web.config檔案中,appSetting的umbracoUseSSL是設為 %0%,您的cookies %0% 標成安全。 - 無法在您的web.config檔案中,更新appSetting的umbracoUseSSL設定,錯誤訊息:%0% - - 開啟HTTPS - 在web.config檔案中,將appSetting的umbracoUseSSL設true。 - 在您的web.config檔案中,appSetting的umbracoUseSSL已設為 true,您的cookies 將被標成安全。 - 修正 - 無法修正比較種類檢查為'ShouldNotEqual'。 - 用提供的數值無法修正比較種類檢查為'ShouldEqual'。 - 沒有提供要修正檢查的數值。 - 偵錯編輯模式關閉。 - 偵錯編輯模式目前已開啟。上線前建議將其關閉。 - 偵錯編輯模式已成功關閉。 - 詳細記錄模式已關閉。 - 詳細記錄模式目前已開啟。上線前建議將其關閉。 - 詳細記錄模式已成功關閉。 - 所有資料夾已有正確權限設定。 - - %0%。]]> - %0%。如果無須寫入,不需採取行動。]]> - 所有檔案已有正確權限設定。 - - %0%。]]> - %0%。如果無須寫入,不需採取行動。]]> - X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd已找到。]]> - X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd沒有找到。]]> - 調整設定的標頭 - 在 web.config 的 httpProtocol/customHeaders 區域增加設定來防止本站被別的網站IFRAMEd。 - 在 web.config 的 httpProtocol/customHeaders 區域已經增加設定來防止本站被別的網站IFRAMEd。 - 無法更新web.config檔案,錯誤:%0% - - %0%。]]> - 在標頭中沒有找到揭露網站技術的資訊。 - 在 Web.config 檔案中,找不到 system.net/mailsettings。 - 在 Web.config 檔案中的 system.net/mailsettings,沒有設定 host 。 - SMTP設定正確,而且服務正常運作。 - SMTP伺服器 %0% : %1% 無法連接。請確認在Web.config 檔案中 system.net/mailsettings 設定正確。 - %0%。]]> - %0%。]]> - - - 停止網址追蹤器 - 啟動網址追蹤器 - 原本網址 - 轉址成 - 沒有任何轉址 - 當發佈後的頁面改名或移動時,會自動轉址至新網頁。 - 移除 - 您確定要移除從 %0% 到 %1% 的轉址嗎? - 轉址已移除。 - 移除轉址錯誤。 - - 您確定要停止轉址追蹤器? - 轉址追蹤器已停止。 - 停止轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - 轉址追蹤器已開啟。 - 啟動轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 - -
From 08818c63588f1e16c993d4ea32518c86dac14d46 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Fri, 5 Oct 2018 20:15:40 +1000 Subject: [PATCH 066/278] removes ng-show from iframe, make loader background a solid color to hide iframe while it loads --- src/Umbraco.Web.UI.Client/src/less/canvas-designer.less | 4 ++-- src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 78c1616bc6..cbb38a23b1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -81,9 +81,9 @@ a, a:hover{ .wait { display: block; - height: 280px; + height: 100%; width: 100%; - background: center center url(../img/loader.gif) no-repeat; + background:#fff center center url(../img/loader.gif) no-repeat; } /****************************/ diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 02209063fd..7f442cc333 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -20,7 +20,7 @@ @Html.Partial(Model.PreviewExtendedHeaderView) } -
+
From 6275a481aa22baeb28b2bace698e3f66da21cc97 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Oct 2018 10:21:32 +0200 Subject: [PATCH 067/278] Fixed order by "updater" query. --- .../Persistence/Repositories/Implement/DocumentRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index d888f452ef..ebe86fe126 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -722,7 +722,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : filterClause.Item1; filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)," : $"AND ({where})", filterClause.Item2); } } @@ -861,7 +861,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin("updaterUser").On((version, user) => version.UserId == user.Id, aliasRight: "updaterUser"); // see notes in ApplyOrdering: the field MUST be selected + aliased - sql = Sql(InsertBefore(sql, "FROM", SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering"), sql.Arguments); + sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName(x => x.UserName, "updaterUser") + " AS ordering "), sql.Arguments); sql = InsertJoins(sql, joins); From 471e47e11bb64ee4d0386e8937c02315ff1a1f64 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 10:41:31 +0200 Subject: [PATCH 068/278] #3163 - Don't overwrite the label name even though labels are disabled in the color picker (#3164) --- .../src/views/components/umb-color-swatches.html | 2 +- .../propertyeditors/colorpicker/colorpicker.prevalues.html | 2 +- src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index a89e51ab32..d038c9973c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,6 +1,6 @@ 
-
From 9b4f879b6d051391c1437e0133719b010ef6feb8 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Tue, 9 Oct 2018 12:28:43 +0200 Subject: [PATCH 075/278] Added XML documentation to the DatabaseSchemaHelper class (#3205) --- .../Persistence/DatabaseSchemaHelper.cs | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index 304cdc185f..f4cf7b23e9 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -11,6 +10,9 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Persistence { + /// + /// Helper class for working with databases and schemas. + /// public class DatabaseSchemaHelper { private readonly Database _db; @@ -18,6 +20,22 @@ namespace Umbraco.Core.Persistence private readonly ISqlSyntaxProvider _syntaxProvider; private readonly BaseDataCreation _baseDataCreation; + /// + /// Intializes a new helper instance. + /// + /// The database to be used. + /// The logger. + /// The syntax provider. + /// + /// A new instance could be initialized like: + /// + /// var schemaHelper = new DatabaseSchemaHelper( + /// ApplicationContext.Current.DatabaseContext.Database, + /// ApplicationContext.Current.ProfilingLogger.Logger, + /// ApplicationContext.Current.DatabaseContext.SqlSyntax + /// ); + /// + /// public DatabaseSchemaHelper(Database db, ILogger logger, ISqlSyntaxProvider syntaxProvider) { _db = db; @@ -26,11 +44,41 @@ namespace Umbraco.Core.Persistence _baseDataCreation = new BaseDataCreation(db, logger); } + /// + /// Returns whether a table with the specified exists in the database. + /// + /// The name of the table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist("MyTable")) + /// { + /// // do something when the table exists + /// } + /// + /// public bool TableExist(string tableName) { return _syntaxProvider.DoesTableExist(_db, tableName); } + /// + /// Returns whether the table for the specified exists in the database. + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// The type representing the DTO/table. + /// true if the table exists; otherwise false. + /// + /// + /// if (schemaHelper.TableExist<MyDto>) + /// { + /// // do something when the table exists + /// } + /// + /// public bool TableExist() { var poco = Database.PocoData.ForType(typeof(T)); @@ -90,6 +138,17 @@ namespace Umbraco.Core.Persistence _logger.Info("Finalized database schema creation"); } + /// Creates a new table in the database based on the type of . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// The type representing the DTO/table. + /// Whether the table should be overwritten if it already exists. public void CreateTable(bool overwrite) where T : new() { @@ -97,6 +156,16 @@ namespace Umbraco.Core.Persistence CreateTable(overwrite, tableType); } + /// + /// Creates a new table in the database based on the type of . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, this method will not do anything. + /// + /// The type representing the DTO/table. public void CreateTable() where T : new() { @@ -104,6 +173,19 @@ namespace Umbraco.Core.Persistence CreateTable(false, tableType); } + /// + /// Creates a new table in the database for the specified . + /// + /// If has been decorated with an , the name from + /// that attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// If a table with the same name already exists, the parameter will determine + /// whether the table is overwritten. If true, the table will be overwritten, whereas this method will + /// not do anything if the parameter is false. + /// + /// Whether the table should be overwritten if it already exists. + /// The the representing the table. public void CreateTable(bool overwrite, Type modelType) { var tableDefinition = DefinitionFactory.GetTableDefinition(_syntaxProvider, modelType); @@ -189,6 +271,19 @@ namespace Umbraco.Core.Persistence } } + /// + /// Drops the table for the specified . + /// + /// If has been decorated with an , the name from that + /// attribute will be used for the table name. If the attribute is not present, the name + /// will be used instead. + /// + /// The type representing the DTO/table. + /// + /// + /// schemaHelper.DropTable<MyDto>); + /// + /// public void DropTable() where T : new() { @@ -204,6 +299,15 @@ namespace Umbraco.Core.Persistence DropTable(tableName); } + /// + /// Drops the table with the specified . + /// + /// The name of the table. + /// + /// + /// schemaHelper.DropTable("MyTable"); + /// + /// public void DropTable(string tableName) { var sql = new Sql(string.Format( From 9986c10cecbe6c0fd867891815dc5cda05adf724 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 15:46:17 +0100 Subject: [PATCH 076/278] Cleaned up the view by removing inline styles and style tags on the view itself. Removed inline styles in the code behind files Moved all styles into the modals.less stylesheet --- .../src/less/modals.less | 57 +++++++++++++++++++ .../umbraco/dialogs/rollBack.aspx | 43 +------------- .../umbraco/dialogs/rollBack.aspx | 39 +------------ .../umbraco/dialogs/rollBack.aspx.cs | 10 ++-- 4 files changed, 66 insertions(+), 83 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index d4552c3ac5..d8f188dd53 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -105,6 +105,63 @@ border-top: 1px solid @purple-l3; } +.umb-dialog .propertyItemheader { + width: 140px !Important; +} + +.umb-dialog .diffDropdown +{ + width:400px; +} + +.umb-dialog .diffPanel { + height: 400px; +} + + +.umb-dialog .diff { + margin-top: 10px; + height: 100%; + overflow: auto; + border-top: 1px solid #ccc; + border-top: 1px solid #ccc; + padding: 5px; +} +.umb-dialog .diff table{ + width:95%; + max-width:95%; + margin: 0 3px; +} +.umb-dialog .diff table th { + padding: 5px; + width: 25%; + border-bottom: 1px solid #ccc; +} + +.umb-dialog .diff table td { + border-bottom: 1px solid #ccc; + padding: 3px; +} + +.umb-dialog .diff del { + background: rgb(255, 230, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff ins { + background: rgb(230, 255, 230) none repeat scroll 0%; + -moz-background-clip: -moz-initial; + -moz-background-origin: -moz-initial; + -moz-background-inline-policy: -moz-initial; +} + +.umb-dialog .diff .diffnotice { + text-align: center; + margin-bottom: 10px; +} + /*we will always make sure to wrap iframe dialogs in proper padding*/ .umbracoDialog{ width: auto !Important; diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx index 5709b47d10..f596121627 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/rollBack.aspx @@ -13,43 +13,6 @@ var submitOnEnter = true; - @@ -63,7 +26,7 @@ () - + @@ -76,7 +39,7 @@ - +

@@ -84,7 +47,7 @@

- +
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx index 6b67e41ecb..ae4c8dd5b5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx @@ -13,44 +13,7 @@ var submitOnEnter = true; - +
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs index b28e40163b..f6390a9d0b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs @@ -34,8 +34,8 @@ namespace umbraco.presentation.dialogs diffPanel.Visible = true; Document rollback = new Document(currentDoc.Id, new Guid(allVersions.SelectedValue)); - propertiesCompare.Text = "" + ui.Text("general", "name") + ":" + rollback.Text + ""; - propertiesCompare.Text += "" + ui.Text("content", "createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + ui.Text("general", "by") + ": " + rollback.Writer.Name + ""; + propertiesCompare.Text = "" + ui.Text("general", "name") + ":" + rollback.Text + ""; + propertiesCompare.Text += "" + ui.Text("content", "createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + ui.Text("general", "by") + ": " + rollback.Writer.Name + ""; if (rbl_mode.SelectedValue == "diff") lt_notice.Text = ui.Text("rollback", "diffHelp"); @@ -66,14 +66,14 @@ namespace umbraco.presentation.dialogs string cThevalue = library.StripHtml(cP.Value.ToString()); - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; } else { //If no current version of the value... display with no diff. - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; } @@ -81,7 +81,7 @@ namespace umbraco.presentation.dialogs else { //If display mode is html - propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; + propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; } //previewVersionContent.Controls.Add(new LiteralControl("

" + p.PropertyType.Name + "
")); From 7e16cf360a8388db3f030aa00de2c48ab947ab07 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Mon, 8 Oct 2018 23:32:35 +0100 Subject: [PATCH 077/278] Issue with the bottom border looking out of place on the select editor screen fixed --- .../src/less/components/umb-tabs.less | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less index e6773a3eb0..0a243b8978 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tabs.less @@ -1,15 +1,15 @@ -.umb-nav-tabs { - position: absolute; - z-index: 999; -} - -.umb-nav-tabs.-padding-left { - padding-left: 20px; -} - -.umb-tab-content { - padding-top: 20px; - position: relative; - top: 22px; - border-top: 1px solid @purple-l3; -} +.umb-nav-tabs { + position: absolute; + z-index: 999; +} + +.umb-nav-tabs.-padding-left { + padding-left: 20px; +} + +.umb-tab-content { + padding-top: 20px; + position: relative; + top: 31px; + border-top: 1px solid @purple-l3; +} From d1b1a1cf6d8e62bbfa148cf9c959309470753987 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 14:23:09 +0200 Subject: [PATCH 078/278] #3178 - Make the Checkbox/TrueFalse property editor configurable (#3179) --- .../propertyeditors/boolean/boolean.controller.js | 2 +- .../src/views/propertyeditors/boolean/boolean.html | 6 +++++- .../PropertyEditors/TrueFalsePropertyEditor.cs | 13 ++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js index 7689d78431..c574e1424f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js @@ -1,4 +1,4 @@ -function booleanEditorController($scope, $rootScope, assetsService) { +function booleanEditorController($scope) { function setupViewModel() { $scope.renderModel = { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 10f388a0d0..ad6bc43cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,6 +1,10 @@

+ on-click="toggle()" + show-labels="{{model.config.labelOn ? 'true': 'false'}}" + label-position="right" + label-on="{{model.config.labelOn}}" + label-off="{{model.config.labelOn}}">
diff --git a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs index 6a8e726911..350e80f4cb 100644 --- a/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TrueFalsePropertyEditor.cs @@ -15,6 +15,17 @@ namespace Umbraco.Web.PropertyEditors { [PreValueField("default", "Default Value", "boolean")] public string Default { get; set; } + + public TrueFalsePreValueEditor() + { + Fields.Add(new PreValueField() + { + Description = "Write a label text", + Key = "labelOn", + Name = "Label", + View = "textstring" + }); + } } } -} \ No newline at end of file +} From 5dd6ef18aa90e1b31307ad0db9e8a11ea5d1fb05 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 9 Oct 2018 20:48:20 +0200 Subject: [PATCH 079/278] first round of moving libraries from bower to npm --- src/Umbraco.Web.UI.Client/bower.json | 16 ----- src/Umbraco.Web.UI.Client/gulpfile.js | 84 +++++++++++++++++++++++--- src/Umbraco.Web.UI.Client/package.json | 13 +++- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 9f9c4fff01..abdd680f3b 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -16,16 +16,6 @@ "tests" ], "dependencies": { - "angular": "~1.7.4", - "angular-cookies": "~1.7.4", - "angular-sanitize": "~1.7.4", - "angular-touch": "~1.7.4", - "angular-route": "~1.7.4", - "angular-animate": "~1.7.4", - "angular-i18n": "~1.7.4", - "signalr": "^2.2.1", - "typeahead.js": "~0.10.5", - "underscore": "~1.9.1", "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", "jquery": "2.2.4", @@ -44,7 +34,6 @@ "font-awesome": "~4.2", "animejs": "^2.2.0", "angular-ui-sortable": "0.14.4", - "angular-messages": "^1.7.2", "jsdiff": "^3.4.0" }, "install": { @@ -62,10 +51,6 @@ "bower_components/moment/min/moment-with-locales.min.js", "bower_components/moment/locale/*.js" ], - "underscore": [ - "bower_components/underscore/underscore-min.js", - "bower_components/underscore/underscore-min.map" - ], "jquery": [ "bower_components/jquery/dist/jquery.min.js", "bower_components/jquery/dist/jquery.min.map" @@ -82,7 +67,6 @@ "bower_components/tinymce/tinymce.min.js" ], "angular-i18n": "bower_components/angular-i18n/angular-locale_*.js", - "typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js", "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 7a6d909154..1a6d8ba511 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -202,14 +202,80 @@ gulp.task('dependencies', function () { ); // npm dependencies - // flatpickr - stream.add( - gulp.src([ - "./node_modules/flatpickr/dist/flatpickr.js", - "./node_modules/flatpickr/dist/flatpickr.css"], - { base: "./node_modules/flatpickr/dist" }) - .pipe(gulp.dest(root + targets.lib + "/flatpickr")) - ); + const nodeModules = [ + { + "name": "angular", + "src": ["./node_modules/angular/angular.js"], + "base": "./node_modules/angular" + }, + { + "name": "angular-cookies", + "src": ["./node_modules/angular-cookies/angular-cookies.js"], + "base": "./node_modules/angular-cookies" + }, + { + "name": "angular-sanitize", + "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], + "base": "./node_modules/angular-sanitize" + }, + { + "name": "angular-touch", + "src": ["./node_modules/angular-touch/angular-touch.js"], + "base": "./node_modules/angular-touch" + }, + { + "name": "angular-route", + "src": ["./node_modules/angular-route/angular-route.js"], + "base": "./node_modules/angular-route" + }, + { + "name": "angular-animate", + "src": ["./node_modules/angular-animate/angular-animate.js"], + "base": "./node_modules/angular-animate" + }, + { + "name": "angular-i18n", + "src": ["./node_modules/angular-i18n/angular-i18n.js"], + "base": "./node_modules/angular-i18n" + }, + { + "name": "angular-messages", + "src": ["./node_modules/angular-messages/angular-messages.js"], + "base": "./node_modules/angular-messages" + }, + { + "name": "flatpickr", + "src": [ + "./node_modules/flatpickr/dist/flatpickr.js", + "./node_modules/flatpickr/dist/flatpickr.css" + ], + "base": "./node_modules/flatpickr/dist" + }, + { + "name": "signalr", + "src": ["./node_modules/signalr/jquery.signalR.js"], + "base": "./node_modules/signalr" + }, + { + "name": "typeahead.js", + "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], + "base": "./node_modules/typeahead.js/dist" + }, + { + "name": "underscore", + "src": ["node_modules/underscore/underscore-min.js"], + "base": "./node_modules/underscore" + } + ]; + + // add streams for node modules + nodeModules.forEach(module => { + stream.add( + gulp.src(module.src, + { base: module.base }) + .pipe(gulp.dest(root + targets.lib + "/" + module.name)) + ); + }); //copy over libs which are not on bower (/lib) and //libraries that have been managed by bower-installer (/lib-bower) @@ -256,7 +322,7 @@ gulp.task('dependencies', function () { stream.add( gulp.src("src/views/dashboard/default/*.jpg") .pipe(gulp.dest(root + targets.views + "/dashboard/default")) - ); + ); return stream; }); diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f7e66f8d24..d2963a6d0d 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -5,8 +5,19 @@ "build": "gulp" }, "dependencies": { + "angular": "1.7.5", + "angular-animate": "1.7.5", + "angular-cookies": "1.7.5", + "angular-i18n": "1.7.5", + "angular-messages": "1.7.5", + "angular-route": "1.7.5", + "angular-sanitize": "1.7.5", + "angular-touch": "1.7.5", "flatpickr": "4.5.2", - "npm": "^6.4.1" + "npm": "^6.4.1", + "signalr": "2.2.1", + "typeahead.js": "0.10.5", + "underscore": "1.9.1" }, "devDependencies": { "@babel/core": "^7.1.2", From ee4a252b0742fb4d960d3f6bfa26365351ab5068 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 9 Oct 2018 21:19:55 +0200 Subject: [PATCH 080/278] Revert "Fixed udi's rendered in rte content (#2531)" This reverts commit b0374695b15c4532b3dba1e1f62b2075924ba187. --- .../Configuration/UmbracoConfig.cs | 2 +- .../UmbracoSettings/ContentElement.cs | 22 +++-------- .../UmbracoSettings/IContentSection.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 3 -- .../Web/TemplateUtilitiesTests.cs | 37 ++----------------- src/Umbraco.Tests/packages.config | 1 - .../Templates/TemplateUtilities.cs | 33 +---------------- 7 files changed, 13 insertions(+), 89 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoConfig.cs b/src/Umbraco.Core/Configuration/UmbracoConfig.cs index 5b6d27b9c5..82d90073e8 100644 --- a/src/Umbraco.Core/Configuration/UmbracoConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoConfig.cs @@ -202,4 +202,4 @@ namespace Umbraco.Core.Configuration //TODO: Add other configurations here ! } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 9fe35f7cb5..17523ab3a1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -139,8 +139,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings internal CommaDelimitedConfigurationElement DisallowedUploadFiles { get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); } - } - + } + [ConfigurationProperty("allowedUploadFiles")] internal CommaDelimitedConfigurationElement AllowedUploadFiles { @@ -195,12 +195,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return GetOptionalTextElement("loginBackgroundImage", string.Empty); } } - [ConfigurationProperty("StripUdiAttributes")] - internal InnerTextConfigurationElement StripUdiAttributes - { - get { return GetOptionalTextElement("StripUdiAttributes", true); } - } - string IContentSection.NotificationEmailAddress { get { return Notifications.NotificationEmailAddress; } @@ -319,8 +313,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings IEnumerable IContentSection.DisallowedUploadFiles { get { return DisallowedUploadFiles; } - } - + } + IEnumerable IContentSection.AllowedUploadFiles { get { return AllowedUploadFiles; } @@ -363,11 +357,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings string IContentSection.LoginBackgroundImage { get { return LoginBackgroundImage; } - } - - bool IContentSection.StripUdiAttributes - { - get { return StripUdiAttributes; } - } + } + } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index bfcbabaccd..343e6ae9e1 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -75,8 +75,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool EnablePropertyValueConverters { get; } string LoginBackgroundImage { get; } - - bool StripUdiAttributes { get; } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index abed9b8245..86c36a8d76 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -65,9 +65,6 @@ ..\packages\Examine.0.1.89\lib\net45\Examine.dll - - ..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 96461519d8..f2a04c5f0f 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,6 +1,6 @@ using System; +using System.Linq; using System.Web; -using HtmlAgilityPack; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; using Umbraco.Core.Scoping; +using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; @@ -33,14 +34,6 @@ namespace Umbraco.Tests.Web [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] //this one has an invalid char so won't match [TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] - // with a-tag with data-udi attribute, that needs to be stripped - [TestCase("hello world ", "hello world ")] - // with a-tag with data-udi attribute spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id, that needs to be strippde - [TestCase("hello world ", "hello world ")] - // with a img-tag with data-udi id spelled wrong, so don't need stripping - [TestCase("hello world ", "hello world ")] public void ParseLocalLinks(string input, string result) { var serviceCtxMock = MockHelper.GetMockedServiceContext(); @@ -70,7 +63,7 @@ namespace Umbraco.Tests.Web //setup a quick mock of the WebRouting section Mock.Of(section => section.WebRouting == Mock.Of(routingSection => routingSection.UrlProviderMode == "AutoLegacy")), //pass in the custom url provider - new[] { testUrlProvider.Object }, + new[]{ testUrlProvider.Object }, true)) { var output = TemplateUtilities.ParseInternalLinks(input, umbCtx.UrlProvider); @@ -78,27 +71,5 @@ namespace Umbraco.Tests.Web Assert.AreEqual(result, output); } } - - [Test] - public void StripDataUdiAttributesUsingSrtringOnLinks() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } - - [Test] - public void StripDataUdiAttributesUsingStringOnImages() - { - var input = "hello world "; - var expected = "hello world "; - - var result = TemplateUtilities.StripUdiDataAttributes(input); - - Assert.AreEqual(expected, result); - } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index e458795917..45afd3279c 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -3,7 +3,6 @@ - diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 39ac69989a..a7e6738374 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -1,6 +1,4 @@ -using HtmlAgilityPack; -using System; -using System.Runtime.CompilerServices; +using System; using System.Text.RegularExpressions; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -48,11 +46,6 @@ namespace Umbraco.Web.Templates { if (urlProvider == null) throw new ArgumentNullException("urlProvider"); - if(string.IsNullOrEmpty(text)) - { - return text; - } - // Parse internal links var tags = LocalLinkPattern.Matches(text); foreach (Match tag in tags) @@ -81,11 +74,6 @@ namespace Umbraco.Web.Templates } } - if (UmbracoConfig.For.UmbracoSettings().Content.StripUdiAttributes) - { - text = StripUdiDataAttributes(text); - } - return text; } @@ -114,9 +102,6 @@ namespace Umbraco.Web.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private static readonly Regex UdiDataAttributePattern = new Regex("data-udi=\"[^\\\"]*\"", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. /// @@ -160,21 +145,5 @@ namespace Umbraco.Web.Templates { return text.CleanForXss(ignoreFromClean); } - - /// - /// Strips data-udi attributes from rich text - /// - /// A html string - /// A string stripped from the data-uid attributes - public static string StripUdiDataAttributes(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - - - return UdiDataAttributePattern.Replace(input, string.Empty); - } } } From 91d8b40216b662d40d3b6b15be967ed50ae3ce30 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 9 Oct 2018 21:29:50 +0200 Subject: [PATCH 081/278] move more libraries --- src/Umbraco.Web.UI.Client/bower.json | 10 ++-------- src/Umbraco.Web.UI.Client/gulpfile.js | 15 +++++++++++++++ src/Umbraco.Web.UI.Client/package.json | 3 +++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index abdd680f3b..38f3f67f81 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -30,11 +30,8 @@ "angular-local-storage": "~0.7.1", "moment": "~2.10.3", "ace-builds": "~1.3.0", - "clipboard": "~2.0.0", "font-awesome": "~4.2", - "animejs": "^2.2.0", - "angular-ui-sortable": "0.14.4", - "jsdiff": "^3.4.0" + "angular-ui-sortable": "0.14.4" }, "install": { "path": "lib-bower", @@ -71,11 +68,8 @@ "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", "jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js", - "clipboard": "bower_components/clipboard/dist/clipboard.min.js", - "animejs": "bower_components/animejs/anime.min.js", "jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js", - "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js", - "jsdiff": "bower_components/jsdiff/diff.min.js" + "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js" } }, "devDependencies": { diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 1a6d8ba511..24fb32d038 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -243,6 +243,21 @@ gulp.task('dependencies', function () { "src": ["./node_modules/angular-messages/angular-messages.js"], "base": "./node_modules/angular-messages" }, + { + "name": "animejs", + "src": ["./node_modules/animejs/anime.min.js"], + "base": "./node_modules/animejs" + }, + { + "name": "clipboard", + "src": ["./node_modules/clipboard/dist/clipboard.min.js"], + "base": "./node_modules/clipboard/dist" + }, + { + "name": "jsdiff", + "src": ["./node_modules/diff/dist/diff.min.js"], + "base": "./node_modules/diff/dist" + }, { "name": "flatpickr", "src": [ diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d2963a6d0d..9b5e2b6d49 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -13,6 +13,9 @@ "angular-route": "1.7.5", "angular-sanitize": "1.7.5", "angular-touch": "1.7.5", + "animejs": "2.2.0", + "clipboard": "2.0.0", + "diff": "3.4.0", "flatpickr": "4.5.2", "npm": "^6.4.1", "signalr": "2.2.1", From 4d1b361f8fc722ee296fe1fa0b3b96f4c02a604e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 9 Oct 2018 21:32:54 +0200 Subject: [PATCH 082/278] Style Public access dialog (#3194) --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- .../umbraco/config/lang/en_us.xml | 2 +- .../umbraco/dialogs/protectPage.aspx | 114 +++++++++--------- .../umbraco/controls/dualSelectBox.cs | 9 ++ .../umbraco/dialogs/protectPage.aspx.cs | 55 +++++---- 5 files changed, 100 insertions(+), 82 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 477bce0931..47a3dc74f4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1219,7 +1219,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access 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 b1fa2b84b3..a9db9768cf 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1217,7 +1217,7 @@ To manage your website, simply open the Umbraco back office and start adding con Role based protection - using Umbraco's member groups.]]> + If you wish to control access to the page using role-based authentication, using Umbraco's member groups. You need to create a membergroup before you can use role-based authentication Error Page Used when people are logged on, but do not have access diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx index 49e1a43ccf..bda6db5be3 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx @@ -75,15 +75,19 @@ - - + +
+ +
+ +
-
+
@@ -116,74 +120,72 @@ + +
+ - - -
- - - - -
- -
- - - -
- - -

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

-
-
- - - -

<%= umbraco.ui.Text("publicAccess", "paSelectRoles", UmbracoUser)%>

-
- - - -
- - - - - - - <%=umbraco.ui.Text("paLoginPageHelp")%> - -
- +
+ + + +
- - - - - - - <%=umbraco.ui.Text("paErrorPageHelp")%> - -
- +
+ + +
- - - + +

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

+
+ + + + +

<%= umbraco.ui.Text("publicAccess", "paSelectRoles", UmbracoUser)%>

+
+ + + +
+ + + + + + <%=umbraco.ui.Text("paLoginPageHelp")%> + +
+ +
+ + +
+ + + <%=umbraco.ui.Text("paErrorPageHelp")%> + +
+ +
+ +
+ +
+
- diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs index 34ad91303e..00bf9d0866 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/dualSelectBox.cs @@ -41,6 +41,15 @@ namespace umbraco.controls } } + public new int Height + { + set + { + _possibleValues.Height = new Unit(value); + _selectedValues.Height = new Unit(value); + } + } + protected override void CreateChildControls() { _possibleValues.ID = "posVals"; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs index 04fb9e6bf9..6fe22f0cd3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/protectPage.aspx.cs @@ -41,7 +41,7 @@ namespace umbraco.presentation.umbraco.dialogs protected void selectMode(object sender, EventArgs e) { p_mode.Visible = false; - p_buttons.Visible = true; + p_setup.Visible = true; if (rb_simple.Checked) { @@ -124,14 +124,15 @@ namespace umbraco.presentation.umbraco.dialogs bt_protect.CommandName = "advanced"; } - p_buttons.Visible = true; p_mode.Visible = false; + p_setup.Visible = true; } } // Load up membergrouops _memberGroups.ID = "Membergroups"; _memberGroups.Width = 175; + _memberGroups.Height = 165; var selectedGroups = ""; // get roles from the membership provider @@ -160,7 +161,8 @@ namespace umbraco.presentation.umbraco.dialogs groupsSelector.Controls.Add(_memberGroups); - bt_protect.Text = ui.Text("update"); + bt_selectMode.Text = ui.Text("buttons", "select"); + bt_protect.Text = ui.Text("save"); bt_buttonRemoveProtection.Text = ui.Text("paRemoveProtection"); // Put user code to initialize the page here @@ -266,37 +268,33 @@ namespace umbraco.presentation.umbraco.dialogs } } - feedback.Text = ui.Text("publicAccess", "paIsProtected", new cms.businesslogic.CMSNode(pageId).Text) + "

" + ui.Text("closeThisWindow") + ""; + feedback_text.Text = ui.Text("publicAccess", "paIsProtected", new cms.businesslogic.CMSNode(pageId).Text); - p_buttons.Visible = false; - pane_advanced.Visible = false; - pane_simple.Visible = false; + p_setup.Visible = false; + p_feedback.Visible = true; var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); //reloads the current node in the tree ClientTools.SyncTree(content.Path, true); //reloads the current node's children in the tree ClientTools.ReloadActionNode(false, true); - feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } protected void buttonRemoveProtection_Click(object sender, System.EventArgs e) { int pageId = int.Parse(helper.Request("nodeId")); - p_buttons.Visible = false; - pane_advanced.Visible = false; - pane_simple.Visible = false; + p_setup.Visible = false; Access.RemoveProtection(pageId); - feedback.Text = ui.Text("publicAccess", "paIsRemoved", new cms.businesslogic.CMSNode(pageId).Text) + "

" + ui.Text("closeThisWindow") + ""; + feedback_text.Text = ui.Text("publicAccess", "paIsRemoved", new cms.businesslogic.CMSNode(pageId).Text); + p_feedback.Visible = true; var content = ApplicationContext.Current.Services.ContentService.GetById(pageId); //reloads the current node in the tree ClientTools.SyncTree(content.Path, true); //reloads the current node's children in the tree ClientTools.ReloadActionNode(false, true); - feedback.type = global::umbraco.uicontrols.Feedback.feedbacktype.success; } protected CustomValidator SimpleLoginNameValidator; @@ -312,13 +310,13 @@ namespace umbraco.presentation.umbraco.dialogs protected global::System.Web.UI.HtmlControls.HtmlInputHidden tempFile; ///

- /// feedback control. + /// p_feedback control. /// /// /// Auto-generated field. /// To modify move field declaration from designer file to code-behind file. /// - protected global::umbraco.uicontrols.Feedback feedback; + protected global::System.Web.UI.WebControls.Panel p_feedback; /// /// p_mode control. @@ -329,6 +327,15 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.Panel p_mode; + /// + /// p_setup control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel p_setup; + /// /// pane_chooseMode control. /// @@ -464,15 +471,6 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.PlaceHolder groupsSelector; - /// - /// p_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel p_buttons; - /// /// pane_pages control. /// @@ -581,6 +579,15 @@ namespace umbraco.presentation.umbraco.dialogs /// protected global::System.Web.UI.WebControls.PlaceHolder js; + /// + /// feedback_text control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Literal feedback_text; + } } From fd1a9bb3656739dcacc86095e43844d6ecc09ab0 Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Tue, 9 Oct 2018 21:51:04 +0200 Subject: [PATCH 083/278] #3214 - umbracoUrlAlias with uppercase characters (#3220) --- .../Routing/ContentFinderByUrlAlias.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index e0121a4b3c..a27b9bd5b4 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -54,10 +54,17 @@ namespace Umbraco.Web.Routing alias = alias.TrimStart('/'); var xpathBuilder = new StringBuilder(); - xpathBuilder.Append(XPathStringsDefinition.Root); + if (rootNodeId > 0) + { xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId); + } + else + { + xpathBuilder.Append(XPathStringsDefinition.Root); + } + XPathVariable var = null; if (alias.Contains('\'') || alias.Contains('"')) @@ -92,20 +99,20 @@ namespace Umbraco.Web.Routing { // legacy XML schema case 0: - DescendantDocumentById = "//node [@id={0}]"; + DescendantDocumentById = "id({0})"; DescendantDocumentByAlias = "//node[(" - + "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},')" - + ")]"; + + "contains(concat(',',translate(translate(data [@alias='umbracoUrlAlias'], ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',{0},')" + + " or contains(concat(',',translate(translate(data [@alias='umbracoUrlAlias'], ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',/{0},')" + + ")]"; break; // default XML schema as of 4.10 case 1: - DescendantDocumentById = "//* [@isDoc and @id={0}]"; + DescendantDocumentById = "id({0})[@isDoc]"; DescendantDocumentByAlias = "//* [@isDoc and (" - + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" - + ")]"; + + "contains(concat(',',translate(translate(umbracoUrlAlias, ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',{0},')" + + " or contains(concat(',',translate(translate(umbracoUrlAlias, ' ', ''),'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),','),',/{0},')" + + ")]"; break; default: @@ -133,4 +140,4 @@ namespace Umbraco.Web.Routing #endregion } -} \ No newline at end of file +} From 3aece09e09564c3b7ba75f1e14b2a29cdbf6f89b Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Tue, 9 Oct 2018 22:25:42 +0200 Subject: [PATCH 084/278] 3223 - Minor tweaks and code alignment for the checkbox and color picker styling (#3224) * Make the focus effect on the toggle use the same box-shadow as is already being used in the color picker instead of doing it differently * Use the color variables instead of hard coded color and also make use of LESS' fade function outputting an rgba() color --- .../src/less/components/buttons/umb-toggle.less | 4 ++-- .../src/less/components/umb-color-swatches.less | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index 2ca03e2c79..150963cbb2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -11,8 +11,7 @@ } .umb-toggle:focus .umb-toggle__toggle{ - outline: 0; - box-shadow: 0 0 5px fade(@black, 30%); + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } .umb-toggle__handler { @@ -35,6 +34,7 @@ background: @gray-8; border-radius: 90px; position: relative; + transition: box-shadow .3s; } .umb-toggle--checked .umb-toggle__toggle { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index d8e67444a1..43e62780a3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -4,7 +4,7 @@ .umb-color-box { border: 1px solid @gray-8; - color: white; + color: @white; cursor: pointer; padding: 1px; text-align: center; @@ -19,7 +19,7 @@ justify-content: center; &:hover, &:focus { - box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + box-shadow: 0 1px 3px fade(@black, 12%), 0 1px 2px fade(@black, 24%); } &.active { @@ -56,7 +56,7 @@ padding-top: 10px; .umb-color-box__label { - background: #fff; + background: @white; font-size: 14px; display: flex; flex-flow: column wrap; From afa3e9b5ebbbed65479443011e6b2962913e5c66 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 9 Oct 2018 22:54:51 +0200 Subject: [PATCH 085/278] Improve image cropper UX (#3149) --- .../src/less/property-editors.less | 12 +++++++++- .../imagecropper/imagecropper.controller.js | 22 ++++++++++++++++++- .../imagecropper/imagecropper.html | 11 +++++----- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +++- .../umbraco/config/lang/en_us.xml | 4 +++- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index d14a1abae9..e3a8128671 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -315,7 +315,7 @@ div.umb-codeeditor .umb-btn-toolbar { .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0 5px 5px 0; + margin: 0 0 5px 5px; padding: 5px; } @@ -574,6 +574,16 @@ div.umb-codeeditor .umb-btn-toolbar { } } + .imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + + button { + margin-left: 4px; + } + } + .umb-close-cropper { position: absolute; top: 3px; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index bc2dd91722..47442495f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -36,12 +36,32 @@ angular.module('umbraco') //crop a specific crop $scope.crop = function (crop) { - $scope.currentCrop = crop; + // clone the crop so we can discard the changes + $scope.currentCrop = angular.copy(crop); $scope.currentPoint = undefined; }; //done cropping $scope.done = function () { + if (!$scope.currentCrop) { + return; + } + // find the original crop by crop alias and update its coordinates + var editedCrop = _.find($scope.model.value.crops, function(crop) { + return crop.alias === $scope.currentCrop.alias; + }); + editedCrop.coordinates = $scope.currentCrop.coordinates; + $scope.close(); + }; + + //reset the current crop + $scope.reset = function() { + $scope.currentCrop.coordinates = undefined; + $scope.done(); + } + + //close crop overlay + $scope.close = function (crop) { $scope.currentCrop = undefined; $scope.currentPoint = undefined; }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html index 18f528fbd2..a8f513f006 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -18,8 +18,6 @@
- -
- - Reset - +
+ + + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 0e9fc09bcd..2ce6a31786 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -991,6 +991,8 @@ Mange hilsner fra Umbraco robotten Nulstil + Acceptér + Fortryd diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 47a3dc74f4..89ba5e0828 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1296,11 +1296,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version 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 a9db9768cf..a42d4d0c60 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1294,11 +1294,13 @@ To manage your website, simply open the Umbraco back office and start adding con Enter the link - Reset + Reset crop Define crop Give the crop an alias and its default width and height Save crop Add new crop + Done + Undo edits Current version From 931fcd565a92568259b2038b616c99a2b481d5e7 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 10 Oct 2018 08:26:10 +0200 Subject: [PATCH 086/278] Removed the override of IsPropertyDirty as is not needed. --- src/Umbraco.Core/Models/ContentTypeBase.cs | 49 ++++++++-------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 41d3419f98..9e73205c36 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -363,22 +363,28 @@ namespace Umbraco.Core.Models /// Alias of the to remove public void RemovePropertyType(string propertyTypeAlias) { - //check if the property exist in one of our collections - if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias)) - || _propertyTypes.Any(x => x.Alias == propertyTypeAlias)) - { - //set the flag that a property has been removed - HasPropertyTypeBeenRemoved = true; - } - + //check through each property group to see if we can remove the property type by alias from it foreach (var propertyGroup in PropertyGroups) { - propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias); + if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias)) + { + if (!HasPropertyTypeBeenRemoved) + { + HasPropertyTypeBeenRemoved = true; + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } + break; + } } - if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias)) + //check through each local property type collection (not assigned to a tab) + if (_propertyTypes.RemoveItem(propertyTypeAlias)) { - _propertyTypes.RemoveItem(propertyTypeAlias); + if (!HasPropertyTypeBeenRemoved) + { + HasPropertyTypeBeenRemoved = true; + OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector); + } } } @@ -411,27 +417,6 @@ namespace Umbraco.Core.Models //fixme should we mark this as EditorBrowsable hidden since it really isn't ever used? internal PropertyTypeCollection PropertyTypeCollection => _propertyTypes; - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public override bool IsPropertyDirty(string propertyName) - { - var existsInEntity = base.IsPropertyDirty(propertyName); - if (existsInEntity) return true; - - //check properties types for this alias if it exists - if (PropertyTypeExists(propertyName)) - return PropertyTypes.Any(x => x.IsPropertyDirty(propertyName)); - - //check property groups for this alias if it exists - if (PropertyGroups.Contains(propertyName)) - return PropertyGroups.Any(x => x.IsPropertyDirty(propertyName)); - - return false; - } - /// /// Indicates whether the current entity is dirty. /// From d41251819f0c4a52cd3206b8587e9b3df95ccdb6 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 10 Oct 2018 08:27:20 +0200 Subject: [PATCH 087/278] RemoveItem now returns a bool --- src/Umbraco.Core/Models/PropertyTypeCollection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 53b75f7e48..47710e04cb 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -124,10 +124,11 @@ namespace Umbraco.Core.Models return this.Any(x => x.Alias == propertyAlias); } - public void RemoveItem(string propertyTypeAlias) + public bool RemoveItem(string propertyTypeAlias) { var key = IndexOfKey(propertyTypeAlias); if (key != -1) RemoveItem(key); + return key != -1; } public int IndexOfKey(string key) From 8fdbfea23ffcfa707a3529dddb000f5e286222ab Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 11:33:42 +0200 Subject: [PATCH 088/278] wip publish descendants dialog --- .../components/content/edit.controller.js | 26 +++++- .../services/contenteditinghelper.service.js | 15 +++- .../overlays/publishdescendants.controller.js | 26 ++++++ .../content/overlays/publishdescendants.html | 89 +++++++++++++++++++ .../Umbraco/config/lang/en_us.xml | 1 + 5 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index efb63fe1ac..98e02f3d55 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -170,7 +170,8 @@ saveAndPublish: $scope.saveAndPublish, sendToPublish: $scope.sendToPublish, unpublish: $scope.unpublish, - schedulePublish: $scope.schedule + schedulePublish: $scope.schedule, + publishDescendants: $scope.publishDescendants } }); @@ -628,6 +629,29 @@ } }; + $scope.publishDescendants = function() { + clearNotifications($scope.content); + //before we launch the dialog we want to execute all client side validations first + if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { + var dialog = { + parentScope: $scope, + view: "views/content/overlays/publishdescendants.html", + variants: $scope.content.variants, //set a model property for the dialog + skipFormValidation: true, //when submitting the overlay form, skip any client side validation + submitButtonLabelKey: "buttons_publishDescendants", + submit: function (model) { + model.submitButtonState = "busy"; + clearNotifications($scope.content); + model.submitButtonState = "success"; + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(dialog); + } + }; + $scope.preview = function (content) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 777b336447..a75e8d4a47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica if (!args.methods) { throw "args.methods is not defined"; } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish) { + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unpublish || !args.methods.schedulePublish || !args.methods.publishDescendants) { throw "args.methods does not contain all required defined methods"; } @@ -200,6 +200,16 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica alias: "schedulePublish", addEllipsis: "true" }; + case "PUBLISH_DESCENDANTS": + // Publish descendants - it doesn't have a permission letter so + // the button letter is made unique so it doesn't collide with anything else + return { + letter: ch, + labelKey: "buttons_publishDescendants", + handler: args.methods.publishDescendants, + alias: "publishDescendant", + addEllipsis: "true" + }; default: return null; } @@ -210,7 +220,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica //This is the ideal button order but depends on circumstance, we'll use this array to create the button list // Publish, SendToPublish - var buttonOrder = ["U", "H", "SCHEDULE"]; + var buttonOrder = ["U", "H", "SCHEDULE", "PUBLISH_DESCENDANTS"]; //Create the first button (primary button) //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. @@ -253,6 +263,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica // get picked up by the loop through permissions if( _.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); + buttons.subButtons.push(createButtonDefinition("PUBLISH_DESCENDANTS")); } // if we are not creating, then we should add unpublish too, diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js new file mode 100644 index 0000000000..b6fcb8391f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -0,0 +1,26 @@ +(function () { + "use strict"; + + function PublishDescendantsController($scope, localizationService) { + + var vm = this; + + function onInit() { + + vm.variants = $scope.model.variants; + + if (!$scope.model.title) { + localizationService.localize("buttons_publishDescendants").then(function (value) { + $scope.model.title = value; + }); + } + + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Overlays.PublishDescendantsController", PublishDescendantsController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html new file mode 100644 index 0000000000..a5b035c82c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -0,0 +1,89 @@ +
+ + +
+
+ Click Publish with descendants to publish {{vm.variants[0].name}} + and all content items underneath and thereby making their content publicly available. +
+ +
+
Include drafts
+ +
+ +
+ +
+ +
+ Click Publish with descendants to publish the selected languages + and the same languages of all content items underneath and thereby making their content publicly available. +
+ +
+ +
+ +
Languages
+ +
+ +
+ +
+ +
+ + +
+ + - +
+ +
+
{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}
+
+ +
+
{{notification.message}}
+
+ +
+
+ +
+
+
+ +
+ +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index ae75d7af43..f78e25821d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -118,6 +118,7 @@ Insert macro Insert picture Publish and close + Publish with descendants Edit relations Return to list Save From e2d3cbc8eb0d8bb16807781f17b213ec6292435c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 12:24:00 +0100 Subject: [PATCH 089/278] Decorate other settings trees with the correct grouping --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 2 +- src/Umbraco.Web/Trees/MemberTypeTreeController.cs | 1 + src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs | 2 +- src/Umbraco.Web/Trees/RelationTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/ScriptsTreeController.cs | 1 + src/Umbraco.Web/Trees/StylesheetsTreeController.cs | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 66f92ffdc0..ebe31d91d9 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Macros)] [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class MacrosTreeController : TreeController { diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 56b836ce8a..77eb6e0b24 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -7,6 +7,7 @@ using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup =Constants.Trees.Groups.Settings)] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, null, sortOrder: 2)] public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs index 882cfb2c9f..c874b01244 100644 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Settings, Constants.Trees.PartialViewMacros, null, sortOrder: 8)] [UmbracoTreeAuthorize(Constants.Trees.PartialViewMacros)] [PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] public class PartialViewMacrosTreeController : PartialViewsTreeController { protected override IFileSystem FileSystem => Current.FileSystems.MacroPartialsFileSystem; diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index e35a9a23b6..4930b4dc5e 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.RelationTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.RelationTypes, null, sortOrder: 5)] [Mvc.PluginController("UmbracoTrees")] - [CoreTree] + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] public class RelationTypeTreeController : TreeController { protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs index 97053993b4..08a03ac912 100644 --- a/src/Umbraco.Web/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -6,6 +6,7 @@ using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 10)] public class ScriptsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs index 365f427e18..548e8ae928 100644 --- a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -4,6 +4,7 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Trees { + [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 9)] public class StylesheetsTreeController : FileSystemTreeController { From f1b20ebbb2fdf105f11fe9b31893b74804da35ce Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 12:25:19 +0100 Subject: [PATCH 090/278] Orders the grouped trees by name (which means the custom trees will be last now) Displays custom third party group - only if we have items to show in that group --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7eeb19893e..940f65730a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,7 +27,6 @@ namespace Umbraco.Web.Trees Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) - .OrderByDescending(x => x.Key) .ToList()); @@ -135,13 +134,16 @@ namespace Umbraco.Web.Trees treeGroupName = "thirdPartyGroup"; } - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); - groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); + if (groupNodeCollection.Any()) + { + var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); - rootNodeGroups.Add(groupRoot); + rootNodeGroups.Add(groupRoot); + } } - return rootNodeGroups; + return rootNodeGroups.OrderBy(x => x.Name); } /// From 75d5c7e30caabe79e06834db4d4565a60928b861 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 14:48:54 +0200 Subject: [PATCH 091/278] add translations, autoslect active variant, order by active + clean up --- .../localization/localize.directive.js | 6 ++- .../overlays/publishdescendants.controller.js | 37 +++++++++++++++++++ .../content/overlays/publishdescendants.html | 30 +++++---------- .../Umbraco/config/lang/en_us.xml | 4 +- 4 files changed, 53 insertions(+), 24 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 3833dc50b9..c3093eee9e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/localization/localize.directive.js @@ -31,13 +31,15 @@ angular.module("umbraco.directives") return { restrict: 'E', scope:{ - key: '@' + key: '@', + tokens: '=' }, replace: true, link: function (scope, element, attrs) { var key = scope.key; - localizationService.localize(key).then(function(value){ + var tokens = scope.tokens ? scope.tokens : null; + localizationService.localize(key, tokens).then(function(value){ element.html(value); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index b6fcb8391f..0536863e6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -7,16 +7,53 @@ function onInit() { + vm.includeUnpublished = false; vm.variants = $scope.model.variants; + vm.labels = {}; if (!$scope.model.title) { localizationService.localize("buttons_publishDescendants").then(function (value) { $scope.model.title = value; }); } + + if (vm.variants.length > 1) { + + //now sort it so that the current one is at the top + vm.variants = _.sortBy(vm.variants, function (v) { + return v.active ? 0 : 1; + }); + + var active = _.find(vm.variants, function (v) { + return v.active; + }); + + if (active) { + //ensure that the current one is selected + active.publishDescendants = true; + active.save = true; + } + + } else { + // localize help text for invariant content + vm.labels.help = { + "key": "content_publishDescendantsHelp", + "tokens": [] + }; + // add the node name as a token so it will show up in the translated text + vm.labels.help.tokens.push(vm.variants[0].name); + } } + //when this dialog is closed, reset all 'publish' flags + $scope.$on('$destroy', function () { + for (var i = 0; i < vm.variants.length; i++) { + vm.variants[i].publishDescendants = false; + vm.variants[i].save = false; + } + }); + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index a5b035c82c..ba515c892c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -1,24 +1,17 @@
-
- Click Publish with descendants to publish {{vm.variants[0].name}} - and all content items underneath and thereby making their content publicly available. +

-
Include drafts
@@ -27,21 +20,16 @@
- Click Publish with descendants to publish the selected languages - and the same languages of all content items underneath and thereby making their content publicly available. +

@@ -55,7 +43,7 @@ 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 f78e25821d..b76bed49cd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -226,6 +226,8 @@ Published Published (pending changes)> Publication Status + Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> + Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> Publish at Unpublish at Clear Date @@ -252,6 +254,7 @@ Add another text box Remove this text box Content root + Include drafts: also publish unpublished content items. This value is hidden. If you need access to view this value please contact your website administrator. This value is hidden. What languages would you like to publish? @@ -1148,7 +1151,6 @@ To manage your website, simply open the Umbraco back office and start adding con - Include unpublished subpages Publishing in progress - please wait... %0% out of %1% pages have been published... %0% has been published From 5a4225900548271473bff74e172b707d5d0c85f4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 15:02:03 +0200 Subject: [PATCH 092/278] disable button if nothing is selected --- .../overlays/publishdescendants.controller.js | 20 +++++++++++++++++++ .../content/overlays/publishdescendants.html | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index 0536863e6b..4647dca093 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -5,6 +5,8 @@ var vm = this; + vm.changeSelection = changeSelection; + function onInit() { vm.includeUnpublished = false; @@ -46,6 +48,24 @@ } + /** Returns true if publishing is possible based on if there are un-published mandatory languages */ + function canPublish() { + var selected = []; + for (var i = 0; i < vm.variants.length; i++) { + var variant = vm.variants[i]; + if (variant.publishDescendants) { + selected.push(variant.publishDescendants); + } + } + return selected.length > 0; + } + + function changeSelection(variant) { + $scope.model.disableSubmitButton = !canPublish(); + //need to set the Save state to true if publish is true + variant.save = variant.publishDescendants; + } + //when this dialog is closed, reset all 'publish' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index ba515c892c..285dc1fc84 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -33,7 +33,9 @@
-
Languages
+
+ +
From 6db8f50ce36859b15aa4d4c04b8e3272572e13e7 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:13:46 +0200 Subject: [PATCH 093/278] Right arrow appears when there are child nodes --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 9 +++++++++ src/Umbraco.Web/Trees/MemberGroupTreeController.cs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 66f92ffdc0..11197fffb5 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; using Umbraco.Core.Models; @@ -19,6 +20,14 @@ namespace Umbraco.Web.Trees public class MacrosTreeController : TreeController { + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any macros + root.HasChildren = Services.MacroService.GetAll().Any(); + return root; + } + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs index b9910c7b31..9c8c8ea4e0 100644 --- a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -19,5 +19,13 @@ namespace Umbraco.Web.Trees .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false)); } + + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + //check if there are any groups + root.HasChildren = Services.MemberGroupService.GetAll().Any(); + return root; + } } } From 66333aa5ec7306d8b63da51460b1d92addd8623f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 15:19:29 +0200 Subject: [PATCH 094/278] fix label for schedule publish invariant content --- .../src/views/content/overlays/schedule.html | 2 +- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 0486bf68dd..d510e4b2a3 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 @@ -4,7 +4,7 @@
-

+

diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index ae75d7af43..b2b01c513e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -264,6 +264,7 @@ Ready to Publish? Ready to Save? Send for approval + Select the date and time to publish and/or unpublish the content item. Create a new Content Template from '%0%' From 5c6bfe823d5f6f192834988a50bb4c6dbea05cbc Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:23:40 +0200 Subject: [PATCH 095/278] Configuring the logi behind "Send to Publish". --- .../components/content/edit.controller.js | 88 +++++++++++-------- .../overlays/sendtopublish.controller.js | 5 +- .../views/content/overlays/sendtopublish.html | 2 +- src/Umbraco.Web.UI/web.Template.config | 5 +- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69c871f5ce..f452e6b812 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -46,7 +46,7 @@ $scope.ancestors = anc; }); $scope.$watch('culture', - function(value, oldValue) { + function (value, oldValue) { entityResource.getAncestors(content.id, "document", value) .then(function (anc) { $scope.ancestors = anc; @@ -154,7 +154,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -163,7 +163,7 @@ } // create the save button - if(_.contains($scope.content.allowedActions, "A")) { + if (_.contains($scope.content.allowedActions, "A")) { $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; @@ -231,7 +231,7 @@ } } - function checkValidility(){ + function checkValidility() { //Get all controls from the 'contentForm' var allControls = $scope.contentForm.$getControls(); @@ -240,7 +240,7 @@ //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' //Check property - $name === "contentHeaderForm" - allControls = _.filter(allControls, function(obj){ + allControls = _.filter(allControls, function (obj) { return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); }); @@ -258,26 +258,26 @@ } //Controls is the - function recurseFormControls(controls, array){ + function recurseFormControls(controls, array) { //Loop over the controls for (var i = 0; i < controls.length; i++) { var controlItem = controls[i]; //Check if the controlItem has a property '' - if(controlItem.hasOwnProperty('$submitted')){ + if (controlItem.hasOwnProperty('$submitted')) { //This item is a form - so lets get the child controls of it & recurse again var childFormControls = controlItem.$getControls(); recurseFormControls(childFormControls, array); } else { //We can assume its a field on a form - if(controlItem.hasOwnProperty('$error')){ + if (controlItem.hasOwnProperty('$error')) { //Set the validlity of the error/s to be valid //String of keys of error invalid messages var errorKeys = []; - for(var key in controlItem.$error){ + for (var key in controlItem.$error) { errorKeys.push(key); controlItem.$setValidity(key, true); } @@ -293,7 +293,7 @@ return array; } - function resetNestedFieldValiation(array){ + function resetNestedFieldValiation(array) { for (var i = 0; i < array.length; i++) { var item = array[i]; //Item is an object containing two props @@ -301,7 +301,7 @@ var fieldControl = item.control; var fieldErrorKeys = item.errorKeys; - for(var i = 0; i < fieldErrorKeys.length; i++) { + for (var i = 0; i < fieldErrorKeys.length; i++) { fieldControl.$setValidity(fieldErrorKeys[i], false); } } @@ -309,7 +309,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid @@ -416,7 +416,7 @@ }); } - $scope.unpublish = function() { + $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { @@ -428,9 +428,9 @@ submit: function (model) { model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); - var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + var selectedVariants = _.filter(model.variants, function (variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function (variant) { return variant.language.culture; }); contentResource.unpublish($scope.content.id, culturesForUnpublishing) .then(function (data) { @@ -444,8 +444,8 @@ }, function (err) { $scope.page.buttonGroupState = 'error'; }); - - + + }, close: function () { overlayService.close(); @@ -454,7 +454,7 @@ overlayService.open(dialog); } }; - + $scope.sendToPublish = function () { clearNotifications($scope.content); if (showSaveOrPublishDialog()) { @@ -471,7 +471,23 @@ model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response - console.log("saving need to happen here"); + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, close: function () { overlayService.close(); @@ -483,10 +499,10 @@ } else { $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.sendToPublish, - action: "sendToPublish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -542,10 +558,10 @@ //ensure the publish flag is set $scope.content.variants[0].publish = true; $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.publish, - action: "publish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -600,13 +616,13 @@ else { $scope.page.saveButtonState = "busy"; return performSave({ - saveMethod: $scope.saveMethod(), - action: "save" - }).then(function(){ - $scope.page.saveButtonState = "success"; - }, function () { - $scope.page.saveButtonState = "error"; - }); + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function () { + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; @@ -729,7 +745,7 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function(app) { + $scope.appChanged = function (app) { createButtons($scope.content, app); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index 6816e1430d..d608ed7f27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -34,7 +34,7 @@ if (active) { //ensure that the current one is selected - active.sendToPublish = true; + active.save = true; } } else { @@ -48,7 +48,7 @@ function changeSelection() { var firstSelected = _.find(vm.variants, function (v) { - return v.sendToPublish; + return v.save; }); $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } @@ -73,6 +73,7 @@ $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { vm.variants[i].sendToPublish = false; + vm.variants[i].save = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index 71877cc907..bbe6609713 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -16,7 +16,7 @@ diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 844855f10f..943570376f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,8 +82,9 @@ - - + + + From f0bbce3117ef537ddc3af602f291699afa64a109 Mon Sep 17 00:00:00 2001 From: elitsa Date: Wed, 10 Oct 2018 15:23:40 +0200 Subject: [PATCH 096/278] Configuring the logic behind "Send to Publish". --- .../components/content/edit.controller.js | 88 +++++++++++-------- .../overlays/sendtopublish.controller.js | 5 +- .../views/content/overlays/sendtopublish.html | 2 +- src/Umbraco.Web.UI/web.Template.config | 5 +- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 69c871f5ce..f452e6b812 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -46,7 +46,7 @@ $scope.ancestors = anc; }); $scope.$watch('culture', - function(value, oldValue) { + function (value, oldValue) { entityResource.getAncestors(content.id, "document", value) .then(function (anc) { $scope.ancestors = anc; @@ -154,7 +154,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -163,7 +163,7 @@ } // create the save button - if(_.contains($scope.content.allowedActions, "A")) { + if (_.contains($scope.content.allowedActions, "A")) { $scope.page.showSaveButton = true; // add ellipsis to the save button if it opens the variant overlay $scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false"; @@ -231,7 +231,7 @@ } } - function checkValidility(){ + function checkValidility() { //Get all controls from the 'contentForm' var allControls = $scope.contentForm.$getControls(); @@ -240,7 +240,7 @@ //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' //Check property - $name === "contentHeaderForm" - allControls = _.filter(allControls, function(obj){ + allControls = _.filter(allControls, function (obj) { return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); }); @@ -258,26 +258,26 @@ } //Controls is the - function recurseFormControls(controls, array){ + function recurseFormControls(controls, array) { //Loop over the controls for (var i = 0; i < controls.length; i++) { var controlItem = controls[i]; //Check if the controlItem has a property '' - if(controlItem.hasOwnProperty('$submitted')){ + if (controlItem.hasOwnProperty('$submitted')) { //This item is a form - so lets get the child controls of it & recurse again var childFormControls = controlItem.$getControls(); recurseFormControls(childFormControls, array); } else { //We can assume its a field on a form - if(controlItem.hasOwnProperty('$error')){ + if (controlItem.hasOwnProperty('$error')) { //Set the validlity of the error/s to be valid //String of keys of error invalid messages var errorKeys = []; - for(var key in controlItem.$error){ + for (var key in controlItem.$error) { errorKeys.push(key); controlItem.$setValidity(key, true); } @@ -293,7 +293,7 @@ return array; } - function resetNestedFieldValiation(array){ + function resetNestedFieldValiation(array) { for (var i = 0; i < array.length; i++) { var item = array[i]; //Item is an object containing two props @@ -301,7 +301,7 @@ var fieldControl = item.control; var fieldErrorKeys = item.errorKeys; - for(var i = 0; i < fieldErrorKeys.length; i++) { + for (var i = 0; i < fieldErrorKeys.length; i++) { fieldControl.$setValidity(fieldErrorKeys[i], false); } } @@ -309,7 +309,7 @@ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { - + //Used to check validility of nested form - coming from Content Apps mostly //Set them all to be invalid @@ -416,7 +416,7 @@ }); } - $scope.unpublish = function() { + $scope.unpublish = function () { clearNotifications($scope.content); if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) { var dialog = { @@ -428,9 +428,9 @@ submit: function (model) { model.submitButtonState = "busy"; - - var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; }); - var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; }); + + var selectedVariants = _.filter(model.variants, function (variant) { return variant.save; }); + var culturesForUnpublishing = _.map(selectedVariants, function (variant) { return variant.language.culture; }); contentResource.unpublish($scope.content.id, culturesForUnpublishing) .then(function (data) { @@ -444,8 +444,8 @@ }, function (err) { $scope.page.buttonGroupState = 'error'; }); - - + + }, close: function () { overlayService.close(); @@ -454,7 +454,7 @@ overlayService.open(dialog); } }; - + $scope.sendToPublish = function () { clearNotifications($scope.content); if (showSaveOrPublishDialog()) { @@ -471,7 +471,23 @@ model.submitButtonState = "busy"; clearNotifications($scope.content); //we need to return this promise so that the dialog can handle the result and wire up the validation response - console.log("saving need to happen here"); + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function (data) { + //show all notifications manually here since we disabled showing them automatically in the save method + formHelper.showNotifications(data); + clearNotifications($scope.content); + overlayService.close(); + return $q.when(data); + }, function (err) { + clearDirtyState($scope.content.variants); + model.submitButtonState = "error"; + //re-map the dialog model since we've re-bound the properties + dialog.variants = $scope.content.variants; + //don't reject, we've handled the error + return $q.when(err); + }); }, close: function () { overlayService.close(); @@ -483,10 +499,10 @@ } else { $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.sendToPublish, - action: "sendToPublish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.sendToPublish, + action: "sendToPublish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -542,10 +558,10 @@ //ensure the publish flag is set $scope.content.variants[0].publish = true; $scope.page.buttonGroupState = "busy"; - return performSave({ - saveMethod: contentResource.publish, - action: "publish" - }).then(function(){ + return performSave({ + saveMethod: contentResource.publish, + action: "publish" + }).then(function () { $scope.page.buttonGroupState = "success"; }, function () { $scope.page.buttonGroupState = "error"; @@ -600,13 +616,13 @@ else { $scope.page.saveButtonState = "busy"; return performSave({ - saveMethod: $scope.saveMethod(), - action: "save" - }).then(function(){ - $scope.page.saveButtonState = "success"; - }, function () { - $scope.page.saveButtonState = "error"; - }); + saveMethod: $scope.saveMethod(), + action: "save" + }).then(function () { + $scope.page.saveButtonState = "success"; + }, function () { + $scope.page.saveButtonState = "error"; + }); } }; @@ -729,7 +745,7 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function(app) { + $scope.appChanged = function (app) { createButtons($scope.content, app); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index 6816e1430d..d608ed7f27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -34,7 +34,7 @@ if (active) { //ensure that the current one is selected - active.sendToPublish = true; + active.save = true; } } else { @@ -48,7 +48,7 @@ function changeSelection() { var firstSelected = _.find(vm.variants, function (v) { - return v.sendToPublish; + return v.save; }); $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } @@ -73,6 +73,7 @@ $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { vm.variants[i].sendToPublish = false; + vm.variants[i].save = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index 71877cc907..bbe6609713 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -16,7 +16,7 @@ diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 844855f10f..943570376f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,8 +82,9 @@ - - + + + From 5b3db88df9cff795d4f8641d32bf517db9650d2f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 15:02:50 +0100 Subject: [PATCH 097/278] Fix logic - so that Content Blueprints tree is added to the list of trees (Finding by name/title was not working, so switched to the AdditionalData["treeAlias"] instead --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 940f65730a..4df0ee0f6a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -116,7 +116,7 @@ namespace Umbraco.Web.Trees if (findAppTree != null) { //Now we need to get the 'TreeNode' which is in 'collection' - var treeItemNode = collection.SingleOrDefault(x => x.Name == findAppTree.Title); + var treeItemNode = collection.SingleOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); if (treeItemNode != null) { From c69590fd215efbbd12dd32089aa553440b8893cd Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 15:03:14 +0100 Subject: [PATCH 098/278] Move macro tree from Templating up into Settings group --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index ebe31d91d9..5d0a4f6315 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -15,9 +15,8 @@ namespace Umbraco.Web.Trees [UmbracoTreeAuthorize(Constants.Trees.Macros)] [Tree(Constants.Applications.Settings, Constants.Trees.Macros, "Macros", sortOrder: 4)] [PluginController("UmbracoTrees")] - [CoreTree(TreeGroup = Constants.Trees.Groups.Templating)] - public class - MacrosTreeController : TreeController + [CoreTree(TreeGroup = Constants.Trees.Groups.Settings)] + public class MacrosTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { From 4da6788cb8799e927e7a36282e660a0fe2798867 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 10 Oct 2018 16:27:13 +0100 Subject: [PATCH 099/278] Fix PartialView Tree Controller to display a folder icon as opposed to an article icon for nested folders - looks odd/broken to me otherwise --- src/Umbraco.Web/Trees/PartialViewsTreeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs index ffb1d5e09b..6a65f5dd3e 100644 --- a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Trees { //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - treeNode.Icon = "icon-article"; + treeNode.Icon = "icon-folder"; } } } From b5b162cba16255b5c884ee8848c49dfd5902147e Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Wed, 10 Oct 2018 13:29:51 -0500 Subject: [PATCH 100/278] Adds information about the Umbraco 8 release plan --- .github/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index 83de27c859..cf29f4e527 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,9 +1,18 @@ -_You are browsing the Umbraco v8 branch._ +_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._ _Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md)._ +When is Umbraco 8 coming? +========================= +When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release: +1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors. +2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement +3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco + +Once a track is done, we start releasing previews where we ask people to test the features we believe are ready. While the testing is going on and we gather feedback, we move on to the next track. This doesn't mean that there hasn't already been work done in the area, but that a track focuses on finalizing, polishing and preparing the features for release. + Umbraco CMS =========== The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com) From dd6d292896e342a8f935911958f40627fe98fa6d Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Wed, 10 Oct 2018 13:51:42 -0500 Subject: [PATCH 101/278] Updating installer background --- .../src/assets/img/installer.jpg | Bin 188415 -> 155700 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/installer.jpg index 71b6336220351d09ab8b9dac2f699a699322fd63..6c0515906ffe07866f73d383bf6277d87415236d 100644 GIT binary patch literal 155700 zcmagFXIxXy)+ijRfT8z*fb?DxB1JmVdqNlK0Rd@2qzFin-UOs1gbsmF1*D6jp?4vG zQj}f==^(bt|Gej%=lO7d_g$a%UbEWFtUYVin%RFp{apdDX$5<@0RVb>q5ulO|G?jG z0Q!4@E?yx3A^_<%S{MNMyFzm7;p4}Cic(TO0g_IxzRn0q7hi9wU?)E*8A)j=fRbvk zpOcFh;xUgi!rjA1nfH6wJ6;|SS7lx+sFAdhpC;m=hi)hm0S`4ccM0`!QE=r|RpC(z zRt)y`^F};&;tBSCB?FO=mQj?xqX<#p`8V)hYeTxaDZ=h)|6AAf zn=%;SpL?>t8z{kqG*OvZ&UGVlZGWxIJ|LbUZd;im~e^CcKh9UmPjQ=ZjfO&`?LJEcm z@C`(|Ag;;z{=fh{}5hVqj=BPCD0q;^H}emGVe7;($&LNQBGc49x4xkYHDg| z$jE5PYshJ6D=5gRL$&18dtUP>NeS-r28Qg!{ia>fiLAYuoeZ6`9H9CqO|H}e( zO?g>}raVL%EGMHbCkMW#sc}!?x*r;v5Owe!bq(Hs^SJ)69{>OHNL?$J`e!Qt*VO(8 zb?qIk1}|3+Rr^4|jh&A;3e;M#Nj!4Umj06Yf(DJUo?DS(ueK>C|h zH|d#afk0YjHbzEfMn*PP2BvFZU}fdtwa?;^&7#AQ0%?{{j*9&70IT)O0j7 zbWAj~G)zo%^z?L0x0slinV4<~fLN~qB*4SR#l^+PBP=W+AS?_9OM}7x<-hy42S86j zWsuP(-V=<6aDQ4yaEsr5nZE*{`2i+eAn;d z)>JE^P-rkLvvm$OhJ+qJ6WTWt3~PJ(#i8rz#O?2QN7{Tm5w`@__v_oe9%xHina)s^ zhs2qBmtQ)XnMDqYRxUcfs7ey)u}WkmPOc|yN6SI20p&14%6Z{>tGtyQGQ^M_5=Fi9ot+rVr81~jZC+!C-OkP#TyMmSwRgozVSq7U zAS3^l;5g#RYgx?P;EKs`D#*%GdeyCebKD~-l&aIKUWmc(l>3R7IPlZr=h6CYk$weE zm95L%ZZ9bfr!dTytz@ZC`~>R#FEm8z2|BU*!Qywnv4sLY&H9T_aV6k`uGG+VulM=G zTV0`Cf?VfzP{lbPoagJwwXPtQ+8tTs0_sy&NJC?CTc+ap`2uag+@aj^R*3f#)l3gm zjgK#5^a2job>GN8(9NgLtL2Gzm$=Q4_va49^CJcCXV#9+1L0nU3#m1@OHP)p-u2cN zg-`qSYb82+0zw($%!*g;E60mB^@G&_`BOfGEEht(rY=)=^KYlk7$a2%b~; zJXLfunb+$*Iu$}u+cTf(I)aqV*5lWORVXq_48jIfNZ4jXC>p4(8-PPO5A^~= zfZ+jxwi)*}NFL}3q|a!LW|TOL!t1DHCUKD4FxRho;vQ+Q*w)luj@z1ZT)919!BDD> z^x#aE5qnwFgzht=lN*7->z1Bg;-kk>>^MIOCbX@3LzUZF=xynWmp!k z_R>;35nNHX3J~Gm0QT~JYkRNePxExZ9my}vgqsVlRq)7_c2+L?GvImod zrGj=Zy8q{Wcq!9e3p?={2KDyNw_QR+l z`^kq#`}<)n4f2px#A$Mfr|Ni%5=VrY(yr8c)@UKq@l&fH19(h8=W2-Duh@>}y6)pH z%jzWoOrTOB_IqIOPSCCWRw41Dt_DntV3tza-bS00x8NSCqjdDk$K8vsVxGqA%Rvv8 zezrNUVD9G6eU)f~r@mu^TYu?XkqZdK<@|cwQy^0qb2D;c=dyESSmS&hWF*#a)5I3j8pE>V&`Ot){(!u z4lqr}WpbkFO`IWQUyI>~8}cdAaevz|&wn7YH(+wErLvO9GM$e`-uk7ZcYQ|2Q?K2T zmw0+bCRZOisC)=8&zbGRuBW_gl62;Vbg%rGkp5fj@8&^Kuu~4M7qsnNnpob1kRDZW zoU=%*S~g;V1E0Dxc(S72c?$p5Hk6+2FfIA;6Z8;$)Pv>pC)oa`)_Wry25eC7;*!)7zs#_kfJ%L4on&6-07;{05|fqpNT^Z#f87U%~a10 zN*dP!F-uE7uC|G>6CRUC$LEww^T?N(^zc$8slpO{RruecG>M;?Q`KoVQ?o!nsDZrD zqKyraO45L+Gr`Zs0P5fy#eHGP${Msdf%H$-MCQ03NP%S&saJGk)}gx=Igx&&I5s_` z5Pp!SYV`N8ScxkWLFBC0ygss-=nCiF)8Qxs;IvdVg=%H%sYGg;A1+VQW)b(7mI`x4 zmlc+?Knb~@UTw*d6O|cAXD}E|VqrE9 z&dgiZvc>}Jw88_a`b*JU@=@Zd^hUzqj!V!xbpgV=$oUx-Nn_G!1&J;xl7c6*9d$K7F*$8ayMFVt^x8D%I?h= zB1In~A@?KCKWk?)-vE`5PQc;VU*Gu)ARo2cVPBV6%aT9nm`ih8%bCf~_a&@$okE{B zlq6&FXXW?pf|S0_7~diMPL-R%gy0o*iagExz;fRG%ywvVQT=rj@l;!adY>X(6JEhT z&XKRcI0?7E!P;@c9biWpdyITq7FYh|24{J3{=irYmyyAXa*{*GFLZVSmCyADZxK?W zEm)!i@ON@HgHctLvhTOqI~^?_TCDuY_9`M;jZu6qJBA8#Z4_PRr80bTsJ7tKJXvY( z=C_hqUX3Et+odUCTBQLaUY1%&x)m~Y^Pv?hc=s%j@&NgA6^|@SW?n(api8z%%7BO9LUa| zl(E-*O*?^*q{^Zo{wiu+zf;AI%)Gd0x4b+@Q7DH*EXsjIDFAvQhGfV_zTFmgZ>TF54$ZaAo&e%& z(}lS6tQhPO+8tDw$7FMPmCIU!nK+E6$Pq6-ub%A&(7qfuU|Md4ai+>XZmO59w9HlmTG*?W+W+Huc1R^nd>%{EJIRUXa0KnJfxCFgzEFmx^@y3_7Yxoxcs<=wyq zk4iCY(q&Z(ui+=$x&cHuYHn<#4mgacxUu?J%cEW42s8uSahG0gaA_aLaN&7!#ux^+QW z`KjHeHch7^aOTlS?if4TWQM$?EKfe27iA778!5Jk#z48)!yWY|a}!CkwVXxgGP>(p z5A=9#2{H4V1x5=kfuFgGJdT1KsH?f!ypJgF-5*?gr_$LC^7C$fKmSe6*I|wE{O(g# zx)YtLZ@(;<%DS7698L?ve>YT?IXKv;c3r)7_gZ~q_7k!B!M?@T^<2*j% zwrIQUzfj~k_ebJs;IFcE$;-KzS1a#U?;Mo1%};cM=lUWO_HNK@;m5pN^%TxcBsr!| ze|@D5`cM%YZp`tPk0Kj)VX*3BexA2@8TK2y)o9Q_y>ll$>fLIaC6%$zQP-!Rp5c9e z0nF?=B($sE9Zv9odDVrT2nU*<)O((@H<%Y#E2%YA=Klf`QqsEr0+{mu0!ZMW6T^M0 zFE5(ImfbJW*4+1>^+IFc>No_fN_e*7mwlenC+|J_k^3kd|CuIx@}l9=^W%hO(qBMg zs?oT&^&1Y8_>|f@qWSBoa9hoT53YvCo$9op^Cw!RZWYnQI3)ZGzE#tuJR=EIPR@$! z%pHcTJ$Ta6qY$VAT?l&U{^>7(;W~mQNvFljy&iepRFNiceWtZv(tg$B@-l)?kkh{* z!HRS2^~k&(mMv8SJh))AwvMRpq=fZ5T{3xSMtoHwCW_~DEhLE?Ado3;CNnhCsN+6h zcO=Z>m5xOrH*<7_3#czi3=!A6gLIyUi|he$k@PR_HS|o|QT7^>X*&nUiVV>AIKLTA z8QA9hSZBaaB3-%1lP(1}$D2MYX7_B@GtuLQ4R#m;%Lt^HSJ8Y_EOs|)GcjtMQ1Oxz zPIE@{NT46v^0CX}43J#Z-tB;)9Hg~>>s(x)`4*WRzYWtz+t5jmLte&`= z(p3B!Smqr5K z-p5paoPRcKDjbEA8l(5fxOH!y-XeYzYZ60bI3LUzDwl(OIYDe$hMpVdBgsH!#fC_A zv>^N4RY_RxdfA=7V|?;f$p-H|@9MMgay-NQI(9z5;M!uTE1?r4v8ugO9WHSR{kPo4 zeUW;b9!eb;(cLLjndQE%|K@H)Hd-kk7Jq66_u>3Xu1Fv|d;%FhR?xvTNXp zA{2aH;k#a^I#52v2YNfbeRKX$wl0qPn54aT;4Xh;10-&L&Nxd}eDQ412VyXK5;+zb^d)m(NMAEadr{dCrMQn;FoQG*kSl}vNBb{0UVNiZXb{f}==Nb5BZV=~+jc8^z zEl`s$t#H}ZI`VU0Y^1>SIwQJ8kMC_Npt?qzAO@(ZfOV%9fi%DOBf7Y3`Q`o$Zu1+4 zO7!92(ttS)FIa^wA`1~+Sv2OI8oYqLLPHkDw77?Pg^%V44*(P7 zmCahB%?#6^iX66=U^&o9)Q*xW$<4I5myh}UDl_WYP)5~*)f<9dW)=E9)&4Wb;8n7S zGV1g^klA%=r03>gUyN(szoK;kh4rKyJj=)rvuXo{SP<<4Jj&#;o%tGM;r`O4(ju*^i_@QShv zoNFGPV%;AhXp1F3J$0kj7I$CJp~`EfnGs+2I0@yG3u+Bpsxa%6F5wu>qx1|wKggjTvxDT&$_iAm$p^?Eo!S5 z%%QR1ak&=2X2nSz_PV;FwbEJ!;{4z=*_0 zy|`aS?`V7RD{8enZ zM=X^;7@LyAT#aD6N?B96ZFSq8zg^!gmqGlF?Sf6=_ueqy6TA8j=a9bu?u7^4oiDk) zj>6FkO@wUw8~L9$j`Bi(M{tYf&ntA9B)2l?o0xd5eQPv2@)&Dzo^P~m3~mfE|FPif zYkT_OyLYI`Ze6qIu+5_*cixLPB7=8obAq0n@3&YOfY*lJs)ClA+d9|KUzWrjG;@$p z?P89(@Jemz4i2?`<<`8cMkd&{sM*x?PW>d6B=hAvrona z?LX%Cz3Uwbm|rhw#Sc8=Xu<`R%q?BU271HNi_h<)!RJ%gq0yZ~$0&ShYvLrV`zzh_ zUjQTPFQtYfm5Ti_ko>{7{XnDAv4+)s z@byTq+BWduvf;|{=iJfLC)Zict?=&&P2Ufz<&=}&UHW=nS{+e$hG2rij0S(+e=AV= zM)WZV_XI4)tFEYv|zO`rLm0K-=`NWmaous9adYYOTJ!&wdcxV}5?L zIplmI_Udt|f~%4269=#MLUPr(jhCR4l*@L|Z}pjDg=6Gt8PoP5i=HL?u zbB=j!zYJi88al#B5$tlXp17RM@yduo8EQS!7LPg%g*+B4JeF0~3X9YKfWn}{kQugay6>8l( ztuIn=lO;*U@Pw^jtY=o8(wPyMwff6Qw!k$Lvz2EEVT!ii86>Cz`qd-ZIX2Ctj)Vtp zr+gtVjk@__a8#h`L*F04)r_0zrqQnMk|0`@GNu%2&D?dcQ$k!be-o5inG#pe*)wcD zr8{LDvMeH33ed+4dy1H_^;%fI0gROINlQ(7fRhgaGk#akw62pTT zj6_B$nIfb`-;hX)hA@UM%ScV~8L2^pQaaJ*D}a?XLS)JtDn4#-m_I`QCHs*LtGG6Y zU}u!@3p~9SZ)l`@NeDF+17Oo;r^hKEM9;mFN7Ox1GvS!!g?x_)4C^?RhP7`Xwj*vu zCke|o$j^Kzf>~c@<=%ucIq%QTOJOvmdn5;jW2t*xjR$kP)IYaQ6nC$jJ?x!#_vD@^ ze@y4o|C6vM_OT)K`G%hK;?id;!qmGoAsZjf{!VAwPz>ejyDq-g@9=a@sX%e#ilu&} z|JOxb%%lBpI>`ME&A8vEg_V~33-#|ON07UZF##JRK3-213jbJRd^Rsp`q<2YI1IJ3 zj?%(ngE*zQV|QbGMtNlkv;4Xkg==+(vuq?zDHy#>w>4_+6vbq*Yv*cfLB29c%m;Kq zskXZQ1sKQXDp4^p+A*+|z0ggl?5{Xmeh|W}h`?#7*szc`mbec{N5$0n*=Y^Py1g@} z=q60VrOpJusBKb>m3n2aBD(FIrL>|@@e`Vc^VQTV_2p-C#A#6sbq4Z6%o8!a9!il6 zJ>7Pb!EJ-ZBF1_o9xZ2P#+gi&w0pNlCuIC9z^;~WUv#7zR(DLoPkz!@O$7{2#l0h; z{2jnFUu(rH$AM-D8C4;YEG=-NpR%m`hpsO^}vYJ6AVhr)b)0h+Tfl58kDjcx9?=~W$-FbEsoT`-k_Jwrm}rX{`35&^y5aoG z24{2R_tW0-TW~Y#QyCRf$aDEG06d0zeXe^HewrXMd*XQC1h!bf$vG_2m>wU!5A-G|U7w`4cT znN=pYio`DrO4AM%Kbq~i-*7$cT66ysj4T_@-e z)IvtO52zI3Rx2iEi#9-&zYI-pVU!rNKF4wUuxZFql${A$^%Z%CMmuD@dt_A_{XB2j z34cbWqKz2SkyIMS!;Nj2cHI&~UeTWe0?s0GW*yT9vR%9 zwwvoZe2}6w=N9hk-c)OUUiC`pd+L68>w>3+zfjXV&+}S|GZm{lf9|WKDaoS8e+KO*l;>-2zmjHvT5ieC zF2pN(YbT`pbY6~XQ}hw>>39e3%&^_|EYB$7YB${Ef}b&g47gtP(Y z-BA8N8GA<-3`mhwI#QqKeDeB!dfkzCflIl0radX935qsI&GC-lRPArZk=^%GB30!pOHL4wnlL zX#N74{BN}8FhhPmy}0xY*uStxEETjXcY6ALJv{$<-|SnDGOj0YVHMG_@2lc>D0%tR z`&nng>e6L+SME35>9gKf?fCukBmJ_w5i%Y7Uvq~J!X!rcRDbey=)As~Ys+e3>uN#I zFGp;C^`7u@KykBo9k$*7lNJ9L;NjiT#<*YP;lqlTum5)E7Oar$_rm#3L*u}SYS!w7 z3P&tWy8qFgS?@HVFFsGs@9VS~8DlR7_I;!lI8(hQUi;W9zm@*A)p40$SLAJS_i0O) z_l%WbMEp{iCTvQ{=lkj=yseh2P}N;|@#5@HU|2va_op#JDCG0OAIUw$eEEso6RAeL zPK~i%+sIgJPMG3;1mLhAJ0I^rLL+Tmz{djqqBUNHym2;J`;kz_I~S#yWM0MTpMqd{ zT*z053C^}4PU>CGaJEb0=P8FKXN(KK-sOxF?h%O#W)YPwFNq&pXCsmjjhYi-sY-O} zF$7khvWd2dj-P!wRlO(e|<-aDtv(3jRGH;ZaNf+W{GffkPfH zwH=wn;*@4Bbw(AJ3sF(ULnqrP7@v3|BRr)_fJZ+-0B4yJ)^po5SEdqHZYt{VLW&4y zMhY(z#y-Qjld(IMFqxto_+_}Ay;Nr-Ijs?rG3?MZtwG)*W%V`c%k+g3g>Whg<47gs zuba`k%&ecv;>e1mUa6m=RKc8Ul0;^3(b5W*r7@>yrjBw3VL`xabe0uZVL zb3%UY8iCvYSob$>fB6H*Wij%rGixa;u42@gD{%PYe(1LQ;vr$wecg`yHk#0$7RY}x zatQ90g{B4*2^W7{Hc&S(tJk(jJr#FvM&e>|%t}t^XNC;VN~{jFR0Lul z_Y`A^D=|(#fRfKIO}SKUD5!FYOE;5Hqy|)G>h8U{rW1oVcHFc~={`?ink z42dcBHj0E1&*zM;Toaox)%b}q}-$hO;!N9F7!Sd>zah$Ky`P1vgy2s%PJ z(4KuBQH6?BzbRc{tr%gKgQFPAk4NkRK9#aywn~@!Y)Yvk6cjjhg(KNk) zb+;BVtw~wk`?eWXgv~^+GeLN7w+BDoX!94{Atcv<*J$l@S@sJ|U%p$B-3TU9RVgMC z`~p&>VPtZP%6ue&046G=LQ>p2n@}1_E3SQgI7i@9Wwei?kF|`ea3_*rjk^xH%_qX6 zg6xrr=G)$tDYHl_$%>cwR8$1{s*7RDJBV3oUVX^q$ekGlHea2%xH1m#T%it!u>*|| z-^4ekGXH^xtDOTmI=6k*9$ss$ z6(@s>2sY1w%~#LFNJCMkRgh)o240J>Xt9Lqw}CuM@9f4FbO+b0Idi5Xt$0gB`u%a) z<-q^%xAbfYz3pm03CjY+F#hXgvkSzv_nD_F$$Yzfbxk1 zKeWF-dMNdjFh%)j-*$8ptm)ZEGFCE#;yH)BkPvpu(wB3}Uxb= zB?4GF^*mp2Y}D)GGQo0Jd|4)1hDM&*IPC_fddIQ_dKDh2KQ$pQM8A%3tcH`?y)LW= z3vAy!6Cu%r%8X}%OHppjLxOKmeToE&F=f{rs8}f}M$AEOb=+Mi$J z_q+4&Xw)c0%>Gea`F19Iyr630+q7_Q{XLChvopcF#m2k!S&P9YqUg?C=~s!mJ0kAS zoIkY&1m7}BcH4F@eyK5;uB!5tVkQY|{GiKluE;B4y-`EfRC_IU7p5R#>P$d#)l-Ke z(1~di~`h zEp>RqfKV@V|5@Yw7r2iiSIhbR_8J}B)Z)UE2Ot)!%JeMdPKWIuUoKYBd9M$jeor#? zpyrPF-PYi1n3)zH>uO zQn={@npubeJy~2%C@UUDO$trkPMS^g12(5{FmF=a%!BcnUHMrb1D?zD%QJ;AhpFA( z7=BJ@pYCxw;~~u~en)L1E*tHaJA0ccn8}HF_W8=04H^9!PIL~OLiIX_ULZp!u58_A zyqNRQ1}ILn7Ox(mW=AfR4u)ry39DI4SmS@Ho26LkWB`SSR(gvzHFfKtq6yKQ<~>Ke zq%DZ+U6jdUnNTzpL+4869{ioaK|gOO;Yya!Nc5FV0w1xEK;`Utz|@TnSOenCns04nSqMR^Hlzt+*R#T15E=EaV4W3R3QZQ5S~$9qPWbd z2<%8)CzH}Yf^NDe)mIoM)EU`I)kg`KSCK5oqy=ZY5U?+H$+YZvaK!-veUnu#bd}4A z8XqW;>tym*8OnZ?n`HU+c|IO`y|uRaD@vnnwCM~!c3WqoqqD*u`6dKuK~_?m zW1(fUjTZ{S&*=8FA~SoUPZ3wdq03UerCXDJv>Ha1ijhOEpvjJPEdfjFIbs6&Gc(wi z#tf8RE~l0!G|L)o7s^&V>NJ>~^NT#pPa6GO-(L`vJtT6>G|SFbT010oRxOaczFrVe zJi2uQ)B%OV4CU@14c}@{<9Z8NOUf6g#S>iu+(kpQ+0j-ZuKq@_jEj&<_QrQY;J|c< zUPq2!WuYSa0w0QM>}W=NFlyTG;CGE_gKK8B!|vyIWDg5>lwf#PtsY~Nkb`-ik2Z}0 zqVv}MM^~0b-z3I0tzQIFU7v9?O()E6vsk{2(0;X(fy(Z&6g%WI5_q z_vde?5w0S;W#%#&VCy*~&o13xd=v{en#6x(f!KsCkGK^8-<-;c6*z5Cn*ajGwGmE+ zqpg27%b0U^w*8xpM(VXW(t8(?pL6X&Z`IZyM(c>T4f0UV8VOgY3I)zIdoe~dj^i?Ason6kXjyoz3XilPk$-R%&#V| zI5GE^l~QoXcs)ED$+Hq&qnio9hMrr@5B1+xBrTgEU`du0d3_!ziX25yT6wCloqWcW z)><#MyJ<*hMms3@#~Y2?>mJqWIf$wWS|c*?ullJ7R%&~Pl`*0 zx*gp8`BCo9!I@OZh4fW?VUr8Q`_F-gtn)XM{0C(n^Ph?iCcoA4@1Ex(aPzuKn2zHP zGa(nBPddcC->kRYg8bLNedJ|M>`)wF6{u3DJcZ_Ef-_ztKzBVP&e_r33<3Ln_vTu+Lvyb~9h)<}TY)l&@t?AqdY!h2r4TwFj zseL$FKfmZLHZi*1IQ--ddon>@%*JIpbRhM(pzHT+%iv{t?*8vKIeNSFkAFVudQ)Z zm65M7$VuL8{2Bl18+sV(ghpZ8%!*>v3QH90sh`~w)gLR`5pfSU*+FXTT)r8x{uiK_ z3#zOZ=8Yz_^}>v1OSILsF~VjzgFw#vZJa$UT=$xU`G?t7mSN51&Nhx_Sv+|f;m_#H z;o`}a3B-l5G&+*vCCGFUVLr3|_8~=QeYiTMfh3)nimG3YAoS3Tf{SiUCxggh7xp7- zMLmJop+sChY30ShwlMd!r1`HKG`=wVdt?SL>X~U_(U2pGj3NQkL8|tc0T9qD(O56jct_7rJV(nO?jDR+*n44<-tJ}=P$1XT)BjL` zH%*$L{O-D-kPvVd39Ts^+_zzm$^!OT%bwx}=i(P{z15P&TSs7>E;o^yc)Ih;5K_Dn zo1!1oc9NGU&Nj?mfGx$6l7BH^t{EJxhIMaa2O2aPJ?L);& zw1IVG9}otr8BGQ?m>n&r^ce}kEX%BQ8_1$w?yY-fr`>}y*9(}MMgG1d^O(9nz+FR~w-{n=gUM%M|`iLtC8i_g|YcmQ{JfdAx*#W17ntMSKz9QjtF-Dk4( z!JQa=3lDvfJB{^%j~-_aVp=x66_)+$l#N7c2z`%JYgIOLRIp64uV*+8%ODT<4C*2_ zvj+AUZ&I!MCZY*-y0d7My)!l=QOv)?Iti=o_L^yp>bNJ*OCt0Ln)~TP;8wzLt+UpM z!G~wWg;?ZAn%joIc?V!C2@lQ&i9LEp&W>1)z)J>f^G%x~=7=_sw)5gqn)ZF$lLG3& zEltnn95>0}RWO!4UPZ7xpro@si^j#Q6Sa&us;|`JY-M*MW7pDmA$PDQ7xs8o!Rals zI&RSICxh${{B5Lt$Nx)iRJ?q1Myr3fpB(2M>e(B#bwnRig$T8n2z*^cJ4(Mfa&$6A zcd6w}e)?IooG;eNF7dh;`Rf%J6EI1rB1(d#6z75(0H_U=cFo5~(b8!{H3EU41>ESF z!rnX;u%0>{w2bvUWvV^Wq6hn3k~(zQMk&1kd}) zTEkN&s0Vbi`SoGRQr%=1f)hw`Eso=Q>?zj3Fo?DexmAd;-5k<37uhT2#;UtNXO_kD zCiZ9AgNT@5L=E+tBK_*H#Hkw=8rCj=(h#DGLUY+hNVb+LuQ>A4ZdJI>S^eHZXRZ6l z{gjFuxT6OHN51njOc;f;kA5 z9X1+{br3qBKo&Ab+o35e0)`z8zS=a`(?neKR&q00uabz3;?hc#Wku;EgjY=uL4R(` z;QxQB<1$A3nSabM;{4eT ziXwwmw#3|O#0mAWeau|kYgt4M4~YDa*tab<6v@OutHL=TmVh%2N|CTw0i{HK+3keN zx!zowST@}`DK2%5qjiJ5s#>jq5+GTQZs{#;PJiap=;oXHFHJo%?nOS;Tu_C1Dp3$C zl7f-~DA2#1iI}fO`A(tC!tCskUcCg>M%n91y!jl7#6ur&IA!HoPluuOJ_dZlbD(Fz zOwG$08#@%ITqB>xGnZ}DdpyXgvna@x!lla>^@debu?frW#q$i+1|hd~%4ZJ@jx!eP zwYAn07tx4`k{T>QmIxH`NTq4KI-_(+-brMx4GEgj|&Vr5%WEdBUlZ~4bny8*{ znnMgHjl(|Jsi`nC2*N)A^?&Kji8y|dN4pW`B}@2==6Dh)$9QTYnR1jIE1}U+*kMs^ zZe5I|V!X|&wZaD(;#2W%s`LZ{hXgJ5Kvr4>rxCFCD(TS9>gA130ugEuTc$sb3j+)} zz(Q`__E2`TI*Ej#qq?%JR28|UIGi&2gk1kwCU-l$x`&mPj(vic*PaL#kSkNJYb>q? z%hE;|WLWpRdRoq6XYFWY*Qb1OV&(M)9{Mh*3Z22ecFQ+LQWPJ6|J0fg`uP^)#?XFr zIG^6guG@f(T{0zMPSX8Wo6Ep~rzN>pdA(LzRz~@h{4DN_=_DK}O{l*|_;E?)4hmem zOzMCeX>()`WiAVGcBE)^&JjyxWqQddGJSkFF2WoxHkrd^?28<in?$+d5=H0Xr9KlHIo&fkxb+lAF(rMj1p;{95pBjjPD^wQ%PIfp_e!=sUz`hYybSjPOjk z)h#JTR-^aBi4y5E;!NHa- z^;$}F13HP>Q57jpXOm-rgjuguGd%$fwD=l>9Fx}&%KzL2-@~!3rX&-cK3Mt&Dy2a6 zQZ9v8zZ~n&tcqjdlInGA9wS$ra8891GcG6Dss=WyrAn2)KMR$+#+{Sv z3!#^J^KNn#ec|8F>VGa>UlTPuB^1)s;C@W>T)gXh=Uw+fRee9m^58>U1=T{$hBTPZ}pM{CLN?9XT;h?4zmd-@6p$E@5C{x=+sq^P* zVs~sC^W|rSc$G1%Se3l;DX{RF*HTVGR5R9(g%tB*JT#V(@^)b!U6gJvv1?=ogzyN@ zvQ75oMafOA9Q#N#TH3=AiZdW~O!hS^<#DU)C8FmfKm<`)ITzKvZYSE3?p$6ae;i~6%>smL%>Mjg|`SwEp0!o#_+K+47>tt@M8+FW? z)>`e-Y8G0}O^eUk;inn$s&|Kz-r4CtrtI5<(|#jf{FpvBE9kCDR0-g6v7y}Ko1haN zsG9DjlExhIz%RZ#^Nk0*fMeA3T0{87vl64>nXJ{-{gUTP})p(I|y^Vu;Wr! z(A{ntgQwvGS%W!!`1+S0sqDqK{sNLCFs90tKEj2j_$0pgb^2ALa#tnMIdl}^w|d^M zMX~;5+kN2&FwYGH`yitD5UHM5H53QKPnL3v|KLc9FY$)pV&TMELj+xD~vz3;qxsM|Nmpf5n zmQ!;^G;n>%k!}DeOEI!)zTU)|s3lvVFYx@5A${=8a>pAruQZoQvA7tR$U_)9ko4-k zi5i#6US5^9f#VlmVvov zlOulBZ!fbjZ(peLmae{+>}#2fQp zSk3!!iEVEB$Or|Q2OfM)8C_bGcqo_{(o!Tj*KNXZv5+{`)lt-#`|c@TDK%n&^-~ee z#c|ay{Ez*97}Mrmp92WZLfBFfA-gs#wh!&K+)}8NloB=YoMHMRP^`ecqePsA)M&RKnGEeH zPSc`NE&IpK5s~s6pBmAe0zJK3NwQy|J!?T4;sNu`fg<47(nl3$l?+pl2iG1ghMZR_ zLnWiG(r6!04cR7q}sCD7A>yvG>-BU^~jQIhnM!Qxi9KDUUVg@CQIQWaY@niw9Z zI~53#;%pMH5l_|MfASl@&|!x)J9n7-3wTunYkmr(jWv^g(i-%Ap{|m~hyBgB{l1~V z@u#Y`^Gowzo4>xEwgpuOlpJ!i{*HO-yk0P_SoGl2uPv`7+w22L!jCr?4v))$Qg12u z?uGC7!{5&S1t5F2ltz$U-orDu3JP}%7Y+#}eynRee3BT2~^PNvDG{!p<=sW8~A z4Ndys!4v@|=GaD=0T&}+5=O1cB4lN=J6uHsM9wy-xzOWgu4YdsHu<<}BR9BQ%^Ww~ zEc5TTM)jGju>iX0wX>9Ub^EQ)^rNkdHM@Jd=FHlSJz8tyt7`+w`F`mp)Y8+^-rZY! zcVD8XGvt1Ay;j+EYR=p`d2I`PW!w2aZ|3`-<=%CoXNh$lwL3l5_rG>tc%5sZO*ymc z@oSA_c(!4~C*m&+bqo%yhd-7@GloUvPC^Xic~RmV@+l~0k&h8r&@!OIAhV3eF#D38 zp-iZz3~7{wXOSk7^#S3S(O1CC(KSfSsQ7@n$XuCppAwYwfE=+b;bapymVSUT+&qUZ z46hHtl-Gg-NxFI#4f6$b>zagj`)`fEaVj=@9jC%v!q&; zYqH$C6^=)DW8!Xq={~Qt_!=ke@jWfCh}hfz063iwF8NgT-!}#9X z>ZqLr=h>X;^7tG672=WC-1vorAEDjYY^CjcZLLne=fc^2ZAPcDvg1#;=hu8i(M2a6 z-xqqCGxQJrlEq8m92@@t2EWjgTn*1@e{WBU_zS*2b!6b;{{V%!uw0M2{{Y#pP93Kn z@4+;^-pU%D%r6V!?(OflBNuGlqp9`VpA_b{99R53C)#wL#823-lzVq1=@Mnw^r7^d zpAxWk^Eht!c}@|z&njLGpFixc;7wPKr?Y>=+^6=O7kp;w%aOUst@&g2wx16x41uMk z4*lOBxUt0?JDslOxTu27ss(FCzmh^XXd ztW{kCpV11@8J&8KIcNh&#B(yG6=qNxda4pjh%Y?~aO0p1wv+H2m#LZLGx$IZtf>H_ z5T!hU(~vTqKH(}!y#esg4rOO3r#kW{^~8WQE-?^T@ha&}LMr{Fxzw5{tvZ2HKLUxy zv5?leFr=zO$O)&=%o?9CkOzf)ODtfWI0LQ#QLaIb9S4|s6$jWOLtlD$h8!3KB3?QP zJzxV8!;uyM5Il=A=R$%b@f@`!iF0HXT?-t7N6RGemY)zv9;7!Me~yIqI9)~0`&FsBr7XNhJ!VqQ(0-^}Tn#LL{eKIVMN zjZUS+Pb+>}_g#aiPI42aN?fSR5M~dRkubMbhe2u3RgBCk$T*>F30gpV)E3ntV(s+1vGlyAEFn?VFCMK{kGdTq`GVy$I zCh7LjzN6GcQ1YgMi{vfUr&P5qb6KLYA24mrV4Pr#w;anD+KF)o9nUuCH(a_LZKmTK zjmYD+%)S&rlIp?ZQm%NKakS{(BNAq?g+hLX8qbj8!8JNbO2mP{mn`5VQHsNe9Ep$D ziJN1W0YO@#qJ2#&vnpR)P^Ty%OOGM?K(Wx9!a5)ng*aKt2KQWzYjU+(ECMQ0zYuTCjjyQ;Tg#Z@tN8=PW=gQLPsD3FvMjD!cO9?pcKd$!b86|ewbbZT(s1$QV(Tw<_1eeP{CDColf@+&4b~U$olIz9aQJd)>CDZsm6)R`>R|7cXh~ zr)&L(e`@6Y&WEkH{k^+=ttQrHFR#4m67lik z0A||Wg}off`&;Yw&+JS1KEL@b#W^qKXV2U7J)Mi)ZtLW7S^bN&xx;F8GyedS<1vfD z_5IIx-8c%qF+96F{{ZB!)#>m#S9JdX*Ft&iFHY%weW!PS`4hlB%<2CC$=g*|C&)kd z9jS+jZzm=1(LZBviEF-`ler_K{{Si7TN-HHy~2OxP9?niV;Jhs?Cl+;xmYPUKa$*2 zo1u*{_3YDt-cLu&*O3NQt4cyYccOx2fXY1(_5Ughcjkm z%DP5Qw~}qw;V|S*fZ2t57TMBr%dbk9X*!xXE7R<72iMH7ho0zs?xj1t2 zc+7mPfsq4oQ^{}3Oy6`Pi!r&&1aP-C*I9^;zjL_@+Pa*(m-}s>1EaZfxyv;sBfl1z zxvb*Nq|uvEp5u9KZ9As9Atqg`qw>Adw%=sZ+)sh|zWe!i9jUf^pE>d>+itdhbCct@ z_g(k4>U}R)hZD-Ub8nmDJ4XiP(&sqpakr#;6-jz%stZQ08X_e@djE~N)+>hGx-y)7;3?m zrWf2|IKi21VMm$SX*Cla13F*{wlcYaXXsO!%rGZePz6}@3iy=y zlYLk!j%>1=3GZf z*~iGr{Y~1Yb0t{as#?_^*Tj5l-@P`lXP?=2?|X42Sw8pRJ~3Hst*T~ueV$(pUAVOJ zy?Z-7kHT4Q-M3tlTu(=DW4GD=01JK1W^U%NRJPm^l{YxV`Fn3|>h(=}eMZy1`YJ6E z%V%ueaN=_8ZTl_vH7($cw>(BZ+_vPo#;jYlw{BkMazn{MS!-$q5OmE$+vaotzJ5+%HmP>{_d0J6_qu9gthcwCnJ* zmi@YKYu!g|aYi3$a{mBStr_0-$bTGEo;!VIxy@(b^DgZ+UN*xx?(CxrB_6Y~*NT%a z#;~Bvr1p0E{%;ol0Eyeau@6RZKZ)A0{_GES?VE1KhJS@y#b{Zxr1tjveD3$d?cS$u zr&atN$<-fH_U*>hlQ^%oDO(; z(CcpO+U0K9)f(5AZCxh%?eP5XxcKk-d7gIv0B3!T=6;FX{65y|cWpyFF7x7V+m!90 z=;QW37B}G6Zn5##Sk$JfalKD-y6hEc2baCuU1P$C_36CX<=>9#>a~%W)0~`_CZ1{x zH0J|x#b&MgWqW1^Jgq72v=beMxh8mNWw32P|P`6ae~?)Oc&DI^%&s_Y81VX1UZjVgX6}6Y&(d z)T{UaIxb`}(4+JK$$AuYE(1rzAe~7Lxs+2-q7Xz=Q&En36jzu=Yo}4-w8WOV7X$z~ zR9fi69I*iVkogsJ0GG=$jYcu`2%aEI>mW_Y#b}2PFNi?+lk+0;dH}9r#G&UXf~;SW zRX#-@qAW4U3UyscdJ$9yol3E(H?ABB)G2-?l$APhD5B6QpDK<^K@%lIq^Oy9(__oYOc}^wKgFep#9Eb(^_n zkn(jd8JLyH7rqxPxe`L{8g4xU-He#KbL#^SaaTrJ7`w)&6E|G(o@O2R)iaLk+kb9m z=B`_EH+BC2jZ8N+R0*r%6JBPpI0n2a~aPMQdr}OQUa>-EPlwIRhCr<)weWhI?6Hj zG_IG-m8o2w>it0`Vr%t~rp0bn<{3>4w^En&#LgO1uy6=Om`NqG!1Sm#{ApO{jcN`;0YkJSch z$U>X&6xWU>Tl`93E?Sdb=9HXsN(}yk3zq%t-Mfx)mT=ZOnzb~&M~OCbE!a*9-R7Py zxtw0s70j1U!F-*yzV$Y{yRE_UI$r4NUiRo?!ey^Fqi27*lzW>;ExrEYf8@_8Ut0L` zH}CL7ym760F!%O8d$`zNw&tEkJ>S`To6?({pD_0vs>B+%b(p+5_rzb^mi2uu*Y+Lj zZkKyKO^@wcaF*&&I@YnA2!|z}At8IVy;Ulrz{{Uv%-lpr0 zDE#qv4_P^F?pmsFX8O9}sl4yq`gYgIY&Gt)dN?zce8*>n*QWJH<~Dqm^{@Qg8n@(k zf8FMD{{U(DrP^(J;rf-G^0`jU%H`&ATi)MwFHJp9&3k{H-`RAz(}~{y0QIl9zR34P z9Pg{Q@LM?kvi%Cp)~c>KE$?=4xx+s<+y2SiE0g!L)$PBuZCs;$C&bP5wwDJdM)w}7@pCkVO>ql?5{tLyOosaDs8`Qt{@kW1ZdG_acb@={osP|?ob!K?m zUHzA@cHZh~|zq{{r zz~V+*R;jtGR}-n+{J(44r~8%HF9UO3w!I^!b<$%l^rdXNC5t>CnfWViS2uJeM2|n@ z{{Xc&W!CM!NcnE}<+r*dW=D6+6^*@V0CEh*l*IZfVw)z^r`L3oJ^mW%0 z4mPs!PsAY$RrC%LZZZ<_#GbyRN!11mT}pzp1L>J?RN_zS03Mzt72+=+uw(@5kBLN6 z%wL4m0HYZ!I2T+7hylFa3nGGogOOwS6jvFn77K!(6}t~3_9j7!$Kl&Jm6-u2Qx5QPz_B<;1l$V ztOY64o_Y+ajPOj&aTM!94NRr?6zL{^NyMb1NwJy6h7(KDgN6iF{EBNB8I-U@uIWKW zr_&Jo1k{tjU}|y+&k6=yidYkVLr=^nMibx$uz;VgVBTdpY6hpMH|2{4SaHY=bF5-3 z%qxKBBvzNxId-jX?q?mk9sdB2*n7LSty!J!C9P{7@AdDG_up;K-bdYR?JbR!w{9(0 zf;`WG?5*0{SG5T#JzK7BTg9IrpOepO_HpZpKNef5&)e-wmx04&zUz+tmaA+_H&$|y zd3MCa`7P#(%EidZ%G9HcqtD5%FXq&&U1Dlc#T18A zuyEd?n03={hXwmN+f#+b-?sHDT+ZU5!M$uwN%fY?|wPiGjBuft=ziTPYjM8 z`>@&Wwr;-Et-WH7ynNPPuA6HyiyxTxeZRH1TuznNq~YRyFUj_Q$92mS!QZ#rb4SwR z-^x53+B`p^fb(kb8*lnyc#P9q62zjWC0X)hRMWr;L+VEtz_@@oB^-xIQK?T* z1;?nYo1YEG^PVAf+&s%Ak{xm%ZOSzU{Rl# zy7i1gJ)_iA5EsfCm7e>RP}S zWl9wr`<9Xg`Zq9M`Ol%pTKW}bz{2@gBHhcWJ=gi1ax)_lJg?b%k3e@c4dhe_l-tiDUIiq& z^WaoV68eHg1QkhR=)$>RYJ7?;=s@@l7XT;XITmx)3$6e(X+Xw!hbm+^CBTDA9BOTG zF>8#g_?k`-8(4E10;xtG04k5re#J-ikZBYFiJ@04NpNL^fbhW#_?b?;Frc=lm<*2y zq6A1TT27ED^x|iqP)+7cH7ioG$Qucdv6kFF)Qgifhg`$mi^K8>`e82&U}19FyFgDp z1Kp8MGg2;D;$BoLls^E5k@SjJEQ+tMflA^6nJAgSlakX=s}3KKsYS?=rFx1Qy7-e} zUSE-7)oJ+L4@X$y(XHyC)Np-Mvw*bjgz85dCo1|;a>uvnkabxz|d!-IX z^4smpx%i$hx~{NkUiE`Fj(sGrq3ynVe`e|TI%0Zue{kM*`)3T#js8a;aJO-$>Spk3 zeiq!m%HQpM$GUy}4^OrJ&9l0f^Z4d|d;G5)2mVFN2Fz{iYe~0{ZjaMH{bKC)>t)TS z#N^uk&)V)kYyGpC`I7H9J+SU(?C*Cia;`bwQe&dao11d|Qr_)vsk3o4&J10~3vfb+7I&YZI=Xp0mGJFDBQ${!gRtf3t6HPqyZGz0b+-ZsT*Q(R+=!XB&E+ zoj7&Zv|`sY-gi3JQ-bIFr%-NsYEEC^b(0L*%%nYiBPaj^a5CLB^pqs3MvKk z{;FK2r8KH3s1nDXFcVB>3y|fYL02Hss$L?e@Sr->3oQayQH=*W6$LTJF!UEnW1(gg zKd21#ku2%t5k+$*+Iljq3`iNPQu7rU>s}=03BEMYAzH{}G|-)E;1zjs8gw(k)tZ$c z9O=Z>5;Ep#L5+L>XFOoE#FM6k_*0k{k3yKjg_UYl`s4$uDOlkklMko^Tu@Lr796z! z#x=-mj;3;|k$8b{#FK^30?w5z=6Ha)TnRtGGJayBUIYrR6gO-0%;QrqX*$z$XsbzKsVp6yq@^pDX00IhZk-McPpi#(p+Cytl#cj4DIJ>9LfwX<&8P~uoO zQ+M4rPPAPNTBhheO}$>(+b+**w=P-PY|hob-M%c-HSqH9wsy58EbLmM*f{pBms6NH z?|uQR5F1Jnb(yYqp+qSib(y$iq$EeCPlifDNl;Gu%tQ?oL8Oe9>+&p&GiF*ja!#38 z$%~XTkj=-NlU(chEMUgs%-CF|9(Lu$aG2e<-DK!;Fo~6@j?&`g(;Je$$7c!+_QPls zhKn8Z^B!gC+jB9cV*Dn?F&(O|#o7_*_^f21Rp7`+AUzSi3-{4J7 z0i0%5LxOl|2p*-GQD?|sm^b;BU|Dd^MOUCKc$bJ1<-}Zd49W5F2uf%I{XnN$GRFX{ z#yXZ9coZ=#X`LGk_!l57cn&xQdJL!d5H#n&r@<11A*1RPs;De=3i8pK^M8m@KZOd2 zyvwhMK)B48E<^PQ%ZS&A7n>+p=vhGkXQ4cN$*npBOOT1MZRJsnbi^x>L`#u*$vz-i zc@$Q2DK9XpA0m=8sgYo~jWAh+VV+=I za78ZxiKl=-15ud$bR}BkOBw_eeM_EV>xW4ux`ky+>LVBg1Yb}ee8nvRa00HST8T9e z01vS|64bMqE*P*3h<=b26h4I|!IA-=)HBqoROkYlpsi3Z0#v9J_zKk*nkcW>3yGfXIAw|otcqucA_TtWKuz*>z@c-WQ+8P{GBH;D4%W`zZ!=nJnwH_cy|o7_vNAVi zka8;->DNaavy0i{X;^ZrF*K|POR#Jkj#+^=OVEZE#2E1AFaJwjG>vDJe;(kiez-nzq@%z9vFOm7kHwtZ;2wYG|HJD+}4doW9zMwevOLo=zWOdp?=Z>@Q=+ z2Z7}4yG%P-*>)JZ$T4(kZ0oeJ2Sb_L91b^bCp)84W*>p1{Bj3qHxl!M7BwqPxY96S zo7S0`oRKDm7cKj+2X=FaN{Un`LSV~j<)FLgGYPzjv4bbwSy+#_<8Pr)QsFY&x~5y# z1`^AN!Iay43Hk)&iQyS&R1&;OYof$`bPVbcH1*^W`+W^UiCF}GrN|m8<}t<~GG0Jj zbj-6VQ$ij60MIm<4E1$yXDh9wF)mya+B$53PBRm=&h z#!C?7#+g0=3k^W2MTW3bSn?>wFcuIrxt4UQ9$}smQAEB0Pr^nr=ub=~06rPyLgb>H zz&wjCG$@W7N-0%_0A9HienQjN#H-=~Q0Mg{gPhASh8)){!8hZG;8`%ZQ;X0VtstOZ z3ItJ(Y@&tsC~1K#Iuxkl$~^oBkp;s*nH9vas1Oe@dAnR}0>oRZW?&*{x?rZ*XZ~3lm3vDc-YCqz0A;qe+^{ zA{37DAu`PaHMHnPLgZ*32Q8*$Rtc&J9&0lyxQJdLc`R+ia;O{JRWnjL%hA{0xAz=J zXMKLu&beLmxz)qn+wR*>L!pK^9;NpCw(dula)wQwuU`KEZJ$-E?fzchb8YD>q456z zF|qC3xcjY_`pnY zH+hQ38iR*}@D}UH9AV;(v1oY)RP!l}U0F(iiWfaWr6cFSyb6oQQ2n)%Db0YjCC|XC zfdNjq{spLhgIUKgB6xAvktVukQxTsKD0vE9IF`O;o;4tntl)?4S?DUCfCBnvxhiB5 zzO*Xgs8tq3T=3I0vcmz%t795N2 zQnCdUN^sy1zQ(XjH7W?+11oIt9J zfZ?4hQmpA15krhT$}2=L>8SbY0m4QT#FcWXuWi?0vBm5`qUtFc*t`Y9-w9)NG$&V0%BAcPNiHBtw~YH6bx%l zK#<0?0)lHy#YSAJ@!zDdmdH4ccie zGW^V^S!Tdgr>>|Y&k@f;)5HsdriIssiB}IHc_)Yh^vsr7@FtdH_oxTpQj!{EP}e{S z{K1;%nVR?jbD%nP7snh7x^fB};pi3SO~b}@&Gcvz1UtGX(Pc9(eGRm2fNYHahj_WB)j(cjyp4vp5sj7~h7V!COzV<}MP+p?2^n=O|xb}X|ZYpYjm1!B*U8fcH(C4;|&7FQ8WU`RDjtROPTQdIu zBAqcoi`OET;scd(#Gj~1Wz#_>I$7ODmYS6FBmA(8WuPd^B}QA9XgAT%Yi`F3^O$@e&{`OU{0m^7a1?6E%Xi=fi;kNWh3TMK#D655`INO zq(brv97x7QimrGBQLkMBDIOx!)^NzM#*t7c&oW#vDC?aH6T~WtdWWjO*P&(;48!m! zLxm<7=^zua0HZ3X(D6 z005Y3b7SW|2fl?>nIe zEDN>xAOtf37 z$JnJtwMUn|Z?|!y)WW?_H-6=I<7i&bPp_XU#guY+KiPJbZj4l(y{XRw&;G@AtG2d( z%iB|2Kdw4*Tt7Pb1q}1BM0?Fyhf8+2ckCS zfyQ{|Q(k2u6jZopfnkE?XAY$e2~A3J@GU@^T=oC-fk& zEJ?y-u$1JWl>Yz%%wy?=G6I7yLtbS5xrAhe$KY9V06^9rVwXZ{pJJG17dBdEIeG;= zK&MnHCF4SbS?WM}4H|i(s(O@ug%bK$jU_>G%&8<)q9ZK{CB+ms_#Lf|*6Z>mvUgc6 z=Yq|d0YI>cw!#MrQ5yyUc9kk%J;O%^Kc&R(PKN#K?pF_3r z`xZq#%^l6XkM`P<@jUx`7vbsC)}NE-_I?|2$L*onUkK8e!$aP;w!8gK4qJ9?TCN){{V!ft6=)Yww^Yj{{W7jk)WQ&(7YPHBXi+fb+qeW@YgIYlkaZq zwr<5#;=kke+JCgv_Ny1w*1iJ&0ExXiC^#w6 zr{-~A@!Qt6;!S?;zifWi)8Wvps*_lGBeQ>LxoW|feZASHqXScEep$NTrwlHnZP?RF zyn(0q7bY2sKdb{Br$c_ z0a2&B@B*Yr^(fYZ#{@tuo5%4Onw2#r4S_;WUr|UhoU}EE1b~(jD@Mx>xQtLER{9!I z`kA#swB!XujUX$_^B<;5eyiC6XO>0gje6pw+x(vS~Q#CO$hCo@!Ci)ggRRXyi;)z7*tcG&vOVblAl^l8( zn)5Czs3p_x2D8McT=9^kX#-jXDgAOPO+h()QGFnWQ?6y7Fve5zC;G4&byOzw323LN z4h}LkwQG0ArY0gzUe!+G$?9EZY%S>YyM4C{h6i_U!-r$bVnttIpUjKE3oZrzrd&f1Wu=WxD^OaFM7kD6ZFRS7@;vR^ zV&{?T?ps68-rI|{Ir3`X9^G7fR@CfUWjSPK%Tdp5nR*jlTH9c)`sQg{Obx3#8de=9 ztYw90t4>8|`%B`UPCsu#_W7K?(e~vKJWnSJw>%8HU*L1QOuK2JF0170UuRQ->rUge z;l9(J+PZ7As_ij-zqOs0X~%t(sm|)@HhKH19tRyB2V;KNsn+0mH<+&4-iJTOflrY| zIHHp(rfRJ>hjbJK4CMMgP*_& zy0K5sFz1O?@hEAJnPtczq2lu(v!zs)T-Z3StaYR=1s6IMFsv38!;BQQ&EOC{3+yxp z!QxRm(vS{wsY+a2kQCOHVN3{}X@C^|L8!k56XFF64Aumv944?!oc?8k*Dx8@r7H{z zpO*px;gMFW(4wUnJpvy&3bk5jTxmhej}WQj#J;4ysw#Sbajz1Mdyclbtax-{{SH50FvZ|*NGDd9y-U^TxUY21r3EBg&AduE=3q*5k%0YF{y9} zQvgkq!X6`=@-y)&U9sI; zT1omfx2@YP-M0(ZneqLuKOOD+dboD!hWmT{n~3kUv00vKqdg6qz58oaa?|NKr)|H1 zSjo-F94So<+V*j4Q0g1 zy;#AFvCC-Apv@Rr;Sa1ssH7slBN>^M-xAzLG7-C*| zhLOezOPoM!;9Bts!Qxz$gkEt{%&|q}1(!@o)A1<$iRYM6)SLY>E?My^<`FF5GLHd? z!BnHjD=&#EVCd+IoS4RJAcdJ>_z#~D=b#o2x&4Ri2vAtEoCRYT6{*EDG_7s@W<_%B zZ9U8452pCPjkEaG`;+xs%soS>8Az5l! zN!r-$?!KoNZ?^BbdY+Azt1b4fr;}~I9_@AhOxv?%s7FMJ=fm3=bUuacE=_I{TjpvG&3B&t`H=!|m*9AAzaQv}W+>Ul+OXihswz;J@J-{{Tt$_ja3E z+Hu|4Sl9T*he_<#FRP_|9sd9ibiz#Q-|<_kDaiZ#zBb39!+*ut)e+NQYw>fwoO}iU z03W))(sA9|ZmcbQkGsF(6xP@}yWbBAi0M7IJL%ck@fYpgZYnBesLh{E-26+d+IgMF z$6ajuRck}GyxQ#HzXsa8sPY~x=G@r6+*3yjUPozMY`{v8pHQbmP~6o}9u!yv!#Tk` z7Xf-faw<@@n&b|7F|HuiAp6IJGAftRZ24~&{~KUtZF z`2mb%%Q+w&y5t5SexjD2QdLyMqg*7JQ#en832zDlbo$IzQ;{1ki-}>#D~V2c@d3|> z&IV;tg+GuC#(f65!J5#pz!@t%7oaj=GQdJ>^^p}4 zjD_j|L6ron_z)QqtK?0Ms-SXOx_FyLZAT*DZbmH^DMw3w#V4SYqp81d?i)^qx2uNB zPHQbKyAFG8txW5yTUJ%3y{`k2SZmsE#hkd+qwP5oHSI1FE-z^NPETnoOX6k3?K15- z-G*JYz|IdRTI*_X?pk*4wacNyxoMrXZ(W^NY%L65Z4Q5J4Bu@hLzUImZ1cD6qdG2* zr>DJb98mLbm2*6t+Ty$M(70$+$djyvDZt|DVQMqup_#)GUMz^Uk&RX%mV63cI0ZA; zLc|p@DW?D;ng<;QUoeV<>C~SfaV1il>KxSaDysOCnXVa-8S2SE=NNGeG3HR=oCX~- zDr1RCT|lwa8DLr0uRw~Ul!aeh3(N|M*8!S`(kjlmlwL&P#{kZ7Ddf2UG}UC3S2D+$ zWvCK;B)L{YJw?u0WCiEMxOkK|IZk8*rz)~_r;$^a9%IKJ$O|IMmCg#3^%(-MP*A@C zuQm!MbK(OF>wzeA@hQ{Ho(zIsMKz$sa0ibN9v&fHsAisZ3~>5_ix`rM%FMIGqHqG{ zKH9BD6rzj7eHF#AX4xZ+g;?%Zl9lf@jlyZpXQ~y|-nFOLttA17pAJcWqmnUB=bcYOh~-K4orf zSmw^(XPn+8L&yrBR{kHz9<#yfA&7--s{eiFl07>n4HrM|EoZ4%4-za7Mj$J2R zbNMlIJL|T;!~TzDI0nv#Z}UGJ++Xb5?k)HK0J8b9{{Z;T^r(}co$a)`{B}lc<+=N9mx4U){l~d`c?)qKjx9X4{3D6g$-7d;(JHsV&8X^pzk6?M+q)(% zxqjn1Yj(0dJe&B(hy9o36L(u{gs8e76Wne)?srako$r+I9{%mmDLlKJ)gN1fkH=Te z&5IUt z5>S#tV$oWSl2D2W&8F2XYf!NSJ ztkKS5>ISCO!t7cZcATwGGYmRP;JV4c;J;};4DGvSsKZI(YF6dT&)x0QpK_ik^ZSpE zw)Fj&p5^v6Tv6A*X*%0RZ(Z#9O|+Hro!7?1r*0>a+;$Gxj|1*^c2&u%*Hg{z{y90i z+Mcd1dpo?GzO=nx6Moh9Kg7e%Fq{v6yzD0^XOF+z-L~qfsqE8|ZTLJKoL5&{$*hYn zQFzW?py4BEK?W1W6Z}E({3@Dsm;RU@z2g;0a9x1vqp< zoUqP>r;Grp)&?fH0{W58o*-%%%{YcLrlV3RSQ}J|m{F}xOJ@L$E;zARNOi{6eu9 zgGk(BWugu@N)6Z%51svb)>4 zTrxMu`se1({{W`F-K(gMO6ezCX8J10*rQT?9d2XCdLhRWCR9YDkuT7&BCk@Xh~l_` z1VqXD1sMv734SAPC?lT5gd0g2+f+Fc=x#Aq3QoT2n~r<#P;gx4>}Xn(xwRbD+G5LR zBc9u6SqoCP@-(f~Nt(iCds0P7(45I;5VDFEH7L{y0x>l}fH8HvIUF0dou-Zr$Yv~R zUaqUQrZ2Xhr(@d8UZ``i(roB%*+;S=Bzar(37NoJI9*i5Vl00XS7t;ua zG%g?%sO7?}n+!g*0?&)cqfb5sK$`0yG{n4^H&+xTh7eG5EMtyC^vWX-%z}SRntb&N zVU|P3GK#a$B9-ETJ!E2(0E$cs{X;%|N$~Ijt0tCGP3p#w0f|RiiDSX!1scYUm(>cG zXj78N0u+756r@VA;x2$#@eV>>UAK(dP^+C%*io{I)z`27} z#)%Wh)TcfGR32SqDLOu7%nOk@*dz@ofMHIvXX^#YXYS$db;mKcF0pu};IcnB2<*nZ?oBU zTWfE#*tW>R`;wmm&2M|rt#r4v^8Wyb^L%&5e?;%Qgtt4kws!Ha9%s?^f7X5nvPx`y z#^TQ}7J3)oe&d_&cXKjd?VlO^-RBS8$KkI}>fM``Z7*+cvu4s*LYLyq-@Utk`QGl~ zKY^DB2bFJLJvHsD{{Wak*{6Y)M#I3&8;J-54M_?C)(~>e_$De_L*$=yu;P*>??DX5!o3ce5!3V8DMh%05aTO z&HF9gxBjeraX4~%oLhC*`zwy#t)HUj@Y(h%{Y%`J6XpAU>$utdy{7wCEH*d)0Cm6o zH{yC7zs_ts{{WA_~TPK&-S82nK)n=}K4`*sU=Ip(NV4Eln<&fbg%%O@fMHz5Ap}QK3qAz&@EYJ8G%5{A zuh3uU!|nw&r4c#T3c+!}lUd`K4KNh{01)T(0-=#yy}sMOaV3PK>^?8ITV1+wTeu%J z@jE}sziP(5huK+bw$*CF)d!Q>+q8PUO>?!sNvs!v=yrQ*+kM)dExSF}qPozgGoX%mXWnf2stcNkj1!-5>m+3i!kSFQ4|zL ztwm!cC`E{ksH|i_G-;wrpv#RSbw=)01c=stSV)aDNmLnSBDqTBX`o0MVIsLu+fV^= z<7O{en_$$UI!fYZFGku6l>?-%BNr;1jTyO8O5Ch)XI}LT>@$ybW?0*=a`z{ysnxnv z`J=ak^*a0Z#nd+$+0gF$YcJb(B@Z-w?|s{*nu_FoR7~3NxgICTYanpA3y+W#DOwGMs`1!8p($ zfJ;<7Fei`&XH!cY(g{*I>px(LEVKMaD)b6-CHesI8sp?#eFZFls|LD+Eo+gbU0$)k zf=TD~BJ`3=5#$vkYs4t1moY$XOz3Lg0A6{FDk2Fb9Q;iv{9qOpz+#oE*8-xf>Ja8w zq~bhEFpg{iCB~r<2B502=oCa^5k%0@vWkJ0weaK+v>dDE4m>j|2+JUAz;ld&=cnXF zVX1#v17D%Gx|n?s8GJe*&Q@Pxim2*Nyk`VPmT{muP=5G?taPDDmmI#}^v_f+Y7Y;% zK>RovZ#68iuYne+Zgab3LBbT?k+j@Qn7N1ocE{7$T z6t8o)y~51y*>t7id3~+^3cX3>%7hmJ zn&?i2UjQm~E@A{x2Cc*rtQxl&zz%)1ayU!WUA0vB zna@?nM{>((a>OG$YML9Qonr{S3O@idLCn5FU|A6qgvp#q0OABF1pr)i_Z%IIj=te4 z&g^TTUu`5s)k%i(=yx&gd=CqC#r`L~-TwfK)bKZ-?|fO~_P!pkUiS;bfi~u$J^?3@ z=xeDe@&wx^={`YCJaPo2^0Wr*}s8Y!~SMdkpQ=S|^v*KI{KO|EW z@ByZUItIEF#voPXo*_)}DbGTquZ#gtktcR?*iT6p^nzt5kGUrt=n2wG%>^=4EO6=) zu+#_Xl{p~yPDrMJDY=e?u7x@1SYuC6Do+BGRa7iMl)iZ~t4x+ZVRXaD3R=SgO=FNp z*CC)5Q6tA4dV?=O6$?!x5>#}n2`Kyk)OavW&KQEP7Buq$$$1VSl|4$BASaAsI5^WF z^MT5sQ_=!-`eO9RAD|S_Fk{G?!^jFSB@q~x5O`^z5^L@?e`F9#90DK}UlPwi3^2q~ zl<6fJsuNE_i(0rU%mfNq)__ijFK^rL_dAPgV#j^&1dmJMKdtxQEqZoZ?ApcdJP*A1 z&xrW{0EydH_Vz8!_5NGW3E`-dKlN{2Ex5*S*Zp5T@c#hO?z+|7e4XFLW-D(q>b5rf zZqIFPc3VqnB_8${vRLyP0hFNa@);^0muoL zSQP||AO}=s<0i7z15^Vg2AD`0VA3=kfXooJfaC^#!5j?$2O|3n5CK5sOaO>LAchDE zsOGbIdwrXhtg>Gv2(@X;hs->^Pe^GyUp)= zp3N%wo{g=ev3=WJ*30ett5)TgD3|_Asmp&~k9(X=Z}f7!cUjVTPwPJ?o3Z`FwA`_O zJ-@y2gB!owduwO-?Y20cPtSi*-P*PHUmth!ZLDor{-?@rUAt}W+veMqE>4W|-%Ck) z#(ekq+Tr#6&-V@MscZJ~JdWr44|BTQ-q$0;{zmirnfGXP*VofuhA!X5-QDlEZ*{r3 zI=61OH8}m!!Mx6z=_5C0z4uI`1wR04?ncm{4djt}uB zynr2g7LnBpOC5R?IpPLHqXKIuKn%+PiNzOM@c^eFq{g|99Sg`HuQ&{N4JDLu-~k#_ zLU=DBdBIU*h#7EHQibsA(75Bks3tY$QTY!X3x*+9FF{W{N}14-;0LGho7V03Rz5kO zUu)`oFK4*+_w93m_PxF8{i`q4DLj7Gy=8j6O|iDq?q%HVn*M8K^rokiWzlka9;LTU zsjSZq{{Ro6rR!XlDZL$sj2&xSG|tJ+w|kVTY{GSuW?88zXDXtDj@SE+P{zRIwL&nl zIRl=^+Eg;JHdZP&l?T`nwf-eyKRn#4kTh|8EAm8sVGdmi^QmNWrZ1JmYI}g;{*@bWRmNdD42|Z(!!O}Nj@GRu!CHg z6#X&!h7!4OW{?c#YTTsH(_DppAUPIBpNtq$95wVdobdq|luEfk=9mZAv!)sjO}sQN zgcU2OLc$6#nrIVDXi(Ad;tkF?6N<(G6Zn8R39O9A=7F$+YZ()NoRj+MRAE_WT+Mt9 zxa4K3@XdjTlwuudPH2lQNYZ~$9*;v@!;waCC&6N(Ml!8TybwhT41kt34h2P>DZm=8 z0{n{yP!Fa)W2_e?Pap_S9Dfm1;8e#F=Pp3*_M44kL(|&6n65{Ov3s`j)Y-rGrh0gH z>4zR&eqGydtQuDxdvDa_w&^_*xZPce9;XVrhSkN(IC3zG`GvO^yHbVYrlP-7KIky- zCRRq|t-hxlzY3YQ`sz%s9kq1~-+9KblyUBELT0>~@n?4Vq1f$z#T^HGcW^xp)y0XK z(&uH`V^?;}Al@exjO4)dIG84gpdun<1VTVW@#I*82pc$eDEJ($oLiX5D^Z1oL9%Sk z%*CxoSG)fJ7sjX0?!VsP_xrY<+j<@b=lQ==$L)MQUcbvc>d9gi$d)?sJda7_Sxl+1SOHHe=1tR% zVB6>FPk=IHCH>z}Y@5gikBk)g1jFFPH{t`q=2&GRF^7<<)G8^*j)dKGWf=rnjO9o9 zhZatApi`5KRsn;T)V0*2HEau?F>gSw13gegvCxvuV2Ws7Y$;BJ+=Kdp&Olym(585P zWi&4^E-xb2fPP~te&8+7)Dnuu1%?y_HTuK_am1_j#Idh|WlAt7qQ?P>NEFp5fEa6m zOUoTg>O(wceE=MA7mVWtm)wB3MjuS5tMyW=G_0d6nJWoD%zdr%IaQp~G_3tOnwm40 z60^+LvZo@k$1`5jUJTuwUd-ADWIhKsv~1eHa^+=--F4Jft|ht>{5tDt-Evl`Zi)}A z{=fY?-OaV#{G8cht-E=%)A$eTUAJRq-*?_y{G07fyKMHazU|AV?ulTR0~nt_$>`$Z zx3Bg80HRLIHkQuCt7~f7D^`E^1W=VRz_6ww*Q;G-vk3{Xz z`DcIe{m)_CJ&xwrZPvVUYlAwvV?*_CmH3X&=C&T*@>cKv0Hbq>`OnOJH+S(p%h}(y zZLhZIqce5G&9=5*`8@T*0-0W9y>tZVMi)0kTs5IW052kRsXStQ3mza&tT+}+Tz;gb zKt-RKEF`qeXjP302x;NemM%vX_YMmz&Y+p+jy!8c<1f z7>A7hMH%au8H(hKDZ`1cs)PVw;e(?_s-zs|%7dhE2AAY9NGPuY0)^)}nun~Z2&4cd zG9E)8ljch(Dl(L7#A3V)l|`v&D2%F}1xd=KQOK!6RE$=lpGBEHbsTbx`H@cBi=_I` zh2LA9ueUo-l1H=feb@2tdoFakjN{L*mbdOT$G3+1?e*XCeLbh8+TZTkOEh_Qo!-lG zF7~_`^S7NV>dTB*bnMvX#C48so3c39TI4pWk%Ly~Wh+^ns|3yymd7@RHjO7!km5Nl zbP>;J(m4(@Y0#`zk)sII(5zK86C*SR6LljpXoeFbGf^l=6RDsxb_N-%olKIo1=t*I z9G5c$ZZUoa8aXd?FdNsi7p%-2mz-hKUd&#rCJtNhM;To;?BU*3IFkL`=yo@iBYYbMH z1#3ak8BH0uWK zaR6;#RKa9kS(0*+;6Sg=bu8tt0ODSO7b?=A<|;FXs8>)asAf(ysT2?Epl3Ob2r=dX zk0*gIuBBGqV4IqB0&Vyi(Hiv!!hmslkkf>$!TJ%T<<1ZeF&9t81Js~z=>}7%=Y*hP z0%gM3?%#9v^g1b5(B#|PcIInM zu~TW7^)0@uGnU)@Pd?4s<0oR>)Z8kg(XS(w(cD*J&8v>Bx0|;9M@h36S7lsY>To!f zzHtUG;$>H|pw-eCq-|R=4n#AsQ_x|2l8{@rMKMM} zP4O{v>SotT96iIGwvPvLx4s9pzjy4pa6DbiUZ;=Qy(81XJF>q*Ra}MZ)RzOw^did~ za3Vld!v;$pc$Qd_q-Ow2z@{fpz?1SUfSRYMXX;N>Qn`UeLj^|#D>AH=6epNr`3tWe z24Y?mzzNne5YL$bX4ghM>Jv>1Eja=nM4D{AnB#^qh*if?!V#H%29!5k$R`t~h0;M_ z$=9dY1vq}8!&3W{=i|ttczr<1%Ab)|gwuGCN+K680j?T^lmMvlCyt1doM=@{1$q@A z%<-NhJy;9VAk)rBfL4VoAi@P>pbPp%Q0LOy;y;)*z>zpL2eW$^HUE1tzv%2n5ySTYcmVdT&59{9p zXJxf-cimd9?r*N$j|84<_MOeYbE|l^a^Gydd>kL@skd{M<+CQHIT<;lXS>$qn`w*RUX|=v{no3s zH*1F{SF+qqJ2S}a)bi~yaQ10)9iBNdP8pr2HpYga#a7MO;h0rIu_HCgZpL9%3dA|r zA-i-kiBXl#xe8<$Cs58BCh`X0%5?@B8KII)7$&BMN@N7q2+21nAY?&x0>&O7A#;RA z(ml;P37$?r$UC>+6Tk_BP9j@SsjS za~8VDPN%8l8Sy8A(3_m+iBBo&5k&(QQcDFOPYxyN#I(Shc6#z304Q~iBUx-$ex`GDwc9ZP|6FasydWedKDvyMI%A!;6W%) ziFxuRj!dz9I02<|O-39JYd2m;1}~VR&8+NMmv-Id(B-#tspl)2;PN>Z>}GV=m7A;4 zw)R(UW~g-*_l|(~(ZAfEY?XKNi_MIN3 zmrCGsC;FXDtIlP04^Fk_vCghEt~gcQO)IoDmTYqSq`k)EK+Z9G>F$9B%6H zFLN18H|{d-@zl(jy5ePUo5wSX>x%pT08@>*W-V$)oz#~D(MI1T|N}~=>gA{UfTyYsw80r$liv@oY)pQ?H z@d&0I1FR*O=N=(1AO@zS`qZ(E%ZWjruR?VE3$HMhG^k23c#R3ChB6t@B5h-iq#lFy zLWw@3l30fa&{WUZilO0J3U%pF%SgnMPXHQzL*fzO$zz~ZBZ`8231a|StRwDTa9-YQ zszG%WvZ7ittgP5%iYjGU9tw<;s~o+0kwnr#%*MI$G}bMHbyJoagd9a@=1}<5pNxT5 z46_NM<3U~$PbBIAzfS6(=07V4=1p@%k6cfS-f(~QE zhzpTHGUfzD0{~DdNg|C16e&gov=AZm1U_Iys)ULwTTJWx&&2F}*JSo@QNLpU0L6B6 zI|i+}+S6GHSl?eM_`fFayDjd^xW8k5-);6k9S*(+=|9`P65o^T{{ZD|TEFD&RmN!i z-P?Vw+jSbn633Tqa4U#Kb&_~f0{an^q^RDKOgi%eQB6rtm!6~Ki>*HbiP2Bi0UC-~6PmcLU`{jYP_rqD;9j5+O<-UvS*t9mpV$k`)v9>r z7OBgpBbM7)WXc6@29>-b4J%DsYLk8`mVLPjONqZ~&+!zEVKT?JsKdbDv{KFn47k0f z9l3ZM?JT+(cI3tkxE-(YK<(&rHP?ZQt*M);%Hax9QOfOKk;e{Bb~Kh#;1OO6?o`FN zfo;=005BpXo;aWmF@_~v$$9veF+eQ7BBv$9 z1MQJIwomnAOn#^awzn?ZxjQKFJ)XnnZn7-hmb}jq)i)Uul+10U+I3l(b1p0HJ6`hJ zqW!CsVtSiB<+sx8S^4L0-tTN(?pHK>U8l|2bS>IEB=T~4{{UTHo>%{jH(u7BwCZE$GoS==Wc>Tx-(y=7E$ zvkm4~HrvSLI_mYT8(Y@k$i>$DIqZ2Fm^eR^G-+O*E*N6#T&cEFr-yu9}mShYo zqY-y#4;I_d7A9?SGXXH$WU(@wp_nRRrr;AY?wXPY8^IWCTLKazisvld&m#f7U@>!+ zYUdOVU$TyI=a(fJ+qWuC=}+}yocYq{in@5uLF>#lQ&--}->m!F$_Og!%Uec?EsYjXIU zyZgTT+mnUQh6wsPYvUg`eX~$39!$SVl_GI?8*>jlqcGtTGfC(a$f&cNvM!V-Il2C5hGBaJ+s2GddZ2-0-uQ=ioWbHhUVc?D_F z!UvMeiBCRY!W>q$YM#y;0FtWis&wR z1{GY2&}vkmZjH+r{DNudA`e|kNv=_tGNNzHFs5ILL!U8#Dsw1PLM~ufVNqIRIWTjE z1N6eA{{UVnu}>%*Ic8X91x$rw_<+d+S6S#BmolUL2DJ)_QlmoZ1tq|tegzyrFe&iR zpNVHe3DS?O7B7wh{2oBy4Oq^_kBQY(dcn7CF_`A9q%8tNcwXs$8ZV zzU)cdaeIN?#APt#_xPB);~w)DUqW4%#NBfStjeKrg-hmdSqT@MV2M?;6zlYFiY|&;UEuGrg9o! z5+w&Bax~}>kOTB8@+hX9#m7(zg)5Mx=Dq;GK!Lb=u8;H^(J&bgRJhdoSz%04?6vXSciB@VU_+qIMtGej~BA zHnvswXMOc&rjoTKYF4F!R7Xj}er_1N+RoDL`Sg#6?Ee6ucWy53x2@Y*+ih9x_TSTf zA7y{!S8qNp5$?f3(s9Z7x!zr`)cl=Z-{QVEwqN*rdo83(QqI-3S!FD?+gAjUAxWCV z`eN;Gduf}sI6J=M`0Rxa=Yh?2-rHs1k23q-3U_;EZ10w|m%mJ4nt^`CF6bVOCjEO`93IWvZyoP`&nq)rLE|EhZ)p_6G4vp4O9CoML&Sv>KtPw6#w3s%+P)lO zoTjx$!~Xzw_!Zi3F8=_xxBmbJ?QY=N_OBTZ=eFAK`#sLnbtbP)Cni4!E=_YY{NIo@ zlS-vmf!+CEj^6m*_Wsv(L$&_b!cLVZG_x}ES%FE=hn;x}=Ukv7Nz7(cFFpd3U>|Wx zUj%6?4iYdk5Gg6(E}wADG%SMl)(Va3sYLWC$N{NZ0!D*}kVbIGsnMEhfUV$68EVyD zc$~J{zZPc8b;Rbj-iKXdHl=FLrjjmWSkiDegvh1?D++QAUShQfXCxeS`;J_eG z6d_Ni5Uj+;KarcQiQ;lc*ESfF1Bu$dLyL0Q;PX2zcyTzlEm;`bUW*-elmo65H1YeH zsm-L`8O{(Hi_Vk4xzaHJp*$l20aF2w9NG*puM(~xDblnlQ|Jnvaufv>u2>E@ z0^x}%0(c9i0i9{ooA3gd=ZNK|a8-_E@PY~}UlCVaonR~Z^DYrJ6vByU?%(?+)#|r<4~SQ_fv@DbW^=y2 zm*J_&yuYgd02kj|^Zm2`04v>dIrjcrX7&3nO#JituV3n}MK zlHTHf#h;(;e`4+|c=SCU+xs=I{{U8xfzNMKY1z_yll@x0$!^;5aLujGsQFuev-auM zr*C8D_BTJm^tdyX_4l6*Piu<*0Qr3ix^Ok`S+m7oEwJ2Ox|8N}S@Iii2ku&<7msQ1 z+~niiw$bW;Bw#-2Jo|gp*X@IAx=$OR&y8#Qe&f2XNL~6JKlxjEyiG3UWq`#zW44~O zFM3B;`K^EIgG+wgF$aT{u$VFYlYy6Zq*^Vmvdpcs=3PpFLKq3IPc!cU|ctY zxqQaAeu_kI4}*5B>9JDJyAE$Jhk{F>|D zxIRz2h3vJ_Vtmhg+&h~{?DxIzyWHkqiRErxEx$eozk`nxtLJla=Ja8_-Z&G()UYh8 z%1=Yi7J*5MkCWm}<`JF+_ZN>m3qBMRG)Q$|$g zD;_Tp6xLjan(+k`AGs*kF-@jrO=5#RK-OGK>`BfAIeCR^l=39$I+d{N1b}7ZUM7>v zLp6yR$EgviftmW$&$&rY3_#mil^L0eKLKu%z?;(`o=jyuLS&+zufP*nyfG%V%$75c zFot?Cl`N(ea3x>FDo;GhpLEKZ<<-~$Yf~SZqHOfXc0*pNi9+FYey2gM@Ii3TJ2~lE6 z1x_SFDN5ud5-tSrUs#Gu2i1W?h$uzp7==3i06A)dIn-QFGUr^-1gVEY&b!%Few@}>Kj5|A5w9eYx!1I&k&zZ}&y4ELMH^Uoi zm!oay+Pb)%J2z)K9E$b#J58LfuFAhosMombTY05F@-?9|oD@65}-U1)JT_?WwOL;IM!w>58fbtUR_cQ1Ec!JFRQ z_ew_&uf+56Z<~i*HNv*MN-`zpJ}_am9SJC-CFWVDoRCwO)kl>xVI4mbk!L{bpmczEkijR<^4e{GyuU%B6x5bN7$gAL0S6Db12If#30PFkE#@5sQq{aXGSpJ z0j5P6fnwvtq2~pxEC&Pu;X!r639KGk6n-QV)nZE-RHg-v1CJQY0|&^l7>w}%65hUM%Lf}!BK@BICMO?xh(z+E?5I&C(#qrdgxN)QvDJ2SmkH!gBC^0iq z4CBWjm_=LcDAi|-*qjuVtt3fe@3~xdyka;ma5yco8~08 z;!KwkX52^2b{`Sn{Eq6|?Y5oMoI-`f`#*^Oi`wifcYZYy3~8+1T0b>|1*l zExT1eYDnZiX z5-KPmf`Tf9FeWc*)gW~{?bu(%YlnBC*0oVZ6hsC0J>A!SD+FpPa^>5uKA%I=;_=tc z_uKvj2qDbm_7VZ|&u1$|#f&B!URd6L`)-(GUv1iSG7&zTV#5ttz!vcwJ9d<2$MU0K;>t{{S9GPCRAj z_VwE7oqjvLB!r0S`KZPAE3J0Arfh*OA3xpp?`L(}lfd9up67krE#0RJ_ghqWjWp{< zao=25)b;s)b5F)}t0PMQfks`5f?1LnAil&fPz1OP5S0T(Ym0^o0PsJ%{6)RzV!QpT zXLn=#<2z5!BdgH;ky~!=-TOO*3}^(F|ecDOQ*iIWpw4(9%^Tm3&}zkS>^qsH*WTN+hyt#NCQ{7+9RPDk)ugn${(9TW+TE(uHbt;#X@JeU7p*k@UwBek`WCfh;Jb zQ`9L*LIkR+Egc~ThpfT#KO zE}cpVaSD``5~)zNm`})pjB_lM;ef(jND5(0(uG{8Ng|5KROv#Z=2 z-)sG?rIZ;;fY*nBy2CDC`FqL#0Fyf#UoYHj{{W58#OSnTr-o)P9KQTJ0+#QgL5E~RlfE$+8$h`GUw>T2BfIVa9v zqS^9$PyYa@mkBwo`3;=zlk;!++v(Eh;&wK^U%A^*t}%T-#kuY8J}2;gi~j&F*ZwAM z?k5&}4Zl0xTK@paoxQ*8cY9*`y-s)2+IDr<-TT$`*LM~ys6XS(^j?v$N0Jl)>&b8?N)>bBaPwF%P$zPwuN+Bj{#*S$Ag?sapKfzFv=O;eH4 zkugxL4qB9Sc?1nsxRg`Kc>}7ifk7?vBBJ{*G4co_P{Hz`#b{Al09G~PON>yVicqyE z%VO0=G>))P5udQqe*w&rU`ggRh{u5?JccH?qAKD(WV{woOO6~W3^ctkEdKxiQq#m^ z^ac%Z&rzo^qH%;`G>$+`C~!4&o<)}u!|p)Ki79>FJQENWkH8Dn1n zZgI?{3p@)t7II5aDd@E({{SN4gABbuvgTZ?CH2HP6mhsGt_Yhz=P=T*P{|}YaY6L~ z6+Z(mw3axu4NQ2Vh)_yIqn82=WE>H4z^O1%YCO7>Q}7DsNhKPW2s&y}80I}fta;{H zXjK&rffD*f>R97I2Y~AxO1%p?0nDp2t`bnNEp}Z?o$&tZo@k7d27tY+r_+-?C^FT01zsB^no)PS30*Fs-VK&>2<;H0tYUt%i9lcR#kBBJ~=FQ!pf ziYdyia4%Q)C;5OW@cWU=XK%Z?Z@F5PtNmAg+f(Mw&d100+l$@1-fh~Z5?0@L{{WL| zJftyQ8sU%RPYx1#H~cpB@BaX`+_QN+Ew$Zk-WSat#?FXea*koem>^k^55-RDe=VT*#4FAmc%=*YOdzeE!*&^C*6;RzqkH9uYTi( z{{Y6+b>kkc?$d#%{XgMtxqJ3*R=iy5UoXKi_{Zk(*;OB;ZvOyJ_-(trw(a|r z(aua)^*@BQ`)=8O=Y?_NJg?H@@sG`_*r}q({VQ|&hrz2F{wuA$>qX5R8-LV39`*kK zr*x>nlgscie+TDv)8-sLoNQRVm;ug>CE zp*<@(qx2R1FX5H(?zt@U4bSO+6K3M}-F4PoujXGb!kDkm@WtWiEw@F1`U3u&@Xe~- zQyZVs{wDOlj_a+hWO;rR#e51?oe8wvUx$(RSN{O}Kkb`#+1z_?Su>}1^exvbZ+_Qx zzv&)R@l039q}?%sel!l=1~th#c*^u8zH`_Go%TYDBiYX1N|$3E1#o~QNS>Kpz0 zx4G`;Yqg_GJGmcy+V-8lX|lFEZMCpp8XC|nIB5HfkKeVH*Np9%#B*hr{z1VB8A6=tLa zAhNG90xZMo2Z{nzEJ|`Lz=mo;oPgE_at|VmfTuB_Wl^tz5Objv{$onCP-Yo&7sE1% zYzQ9}%yQ&J5Edfp>QHKHZ*uD|S6hmxWDxQYvpp;5dtLRtw60xFW5oFG=lHi3jsDxH zdr8UPTbTJ?#~1DIXT|FY5f#KWDDl2WxBflPeXI7}>U$>#b+Wg%ow?*qren9S$>F@e z7o~Vv3dF9p+jF+;Q-(<3`mv>epj<~H1!6E|-j-%w?YULxQOGpp7HxMspW(Lp(B`+} z`=;jmt+8=sn%nTk+`M}Jt>peLDg>PGv+cI)z1*u;Lx%q2;5L{3PV(7b^)JNK;okf? zUvGwd+xbOp$vjUJ`=`Y1_un1=06Ne8e%VzlU8k}C0K5MHS4zFjzx=@dZ~Cp)UAK1q zj@y(p-5}iHbZXR zAO7#;{I(%;EYH+m^-tThFy>nDtvyj#xjoV<7aSp4Jj zPMDNO)Sq|U{{Z_k?i9Y;xK}M2{Lde|{)_Y7vY+Bxi!W<4<7ar>y58GM{9icK1y0{{ z@xAYBbGLtH{kX-n&Z}8QB*A5$0XO5RRwytOOd~DF;w9t(8n$HTu`Ya$y{$@G* zlR9y;vs^rhy>cw+Ov-;#qb$S}@}IFk(5aTR3V4ju!>K%0dVx;7yhl0VLZ-6?ubV{`6x35|GETe;73xJqRs4ys zJV1W<$yG9rq4nYg!3%wBfMSWaBn-;NnuNz8fS;~qQ_H|Bo5vEIhSe@GTtr5#R6`k5 zZMQ6qLN_&MEWrDJLrF^FP-_X1?Q!T485{P;6j0G{nPwX2kTvvM&)-&^4Y~ zvz4teFxF(TWpOnxfJ5*(U9rQNi>+E)(cuB@hCVH zI2wb+#dHFI>M<4aECLNDkwg(d7E{s+A2N6>hM87M1x$1*Oh?iZ&K^8S5>AVldFE+A z6>%xzOlyr;3dD&g_zw^zUYHE#OO0Ts0trlE^oNA?4mj{)-{KKQBULHjQJj*9DyK)l zoYTanrA40_5f>c>vp!+RJ|#kn8o^hIEP+n70a)l#;vCxz*ez5ONmpVVOiwB z;rr+uFehKcr!0b`>cUjUIu;2uy_ zzy~Za8JsJ`C!s`o5F)tv1$r^7&Z|Hv$BAgQB+8*z2T{VBU>MeVm+p=kgf&aS4c|dunX99vZ(hK6B%* z+aC{S{{XZ90F!P%pJpb%re?`nK0Jh48_Ztm^c z?Xj=hOIx*jqnr__8-=kzY}#qa+Bm9+NUMgH5;trz-3 z{{Yl3^*`U-?WEMF?Y<+x`;X?f?X}zAAII)Z-&)$M7h5V$Cv;r>p5?VK*rmm@qHJ~h zKZE2pzoJ~OZ+p5|TCjU|w*1aV{SkXEf6mqWb-61pqI6CBkF5TCuS(t2ueDbMuHzSE zJJ;@RwfvX26Z~%Kw7%u>zjV*xA17z@PTJdj`@P2H%3-T5wtZRK+y0^Owkh`RZpC5U z4t0c&p?6)mYW_<5Y3@{W#Ko~^?YgPbai=fLc>Xu}nfxd6dA9!mT=mv$NQCdseUE+1uNSDYDfG@jEl^yWO9bqV`a9xu1&?! zRHyfH#3Hm&#S3@cYPGj=SS=*D8+Siqr@PwWHwnaMpvt>*+wE6VA8SdZm^*8yG4|?` z^)3+m+po1YsOb`y{i63a{p$M{@iqRLcSGuCUtEm)dDLKO-);BZbGbV!B+b{{Yn*4ZEpIcBgI**{jomm~#q}PPz3SFfVe{ z{kGfE`uJc?tFD_=T9h?1ak!e*nzr3Mh8GS0044GMO{J4_>n0bqG~6yl5b7{f3|x{^ zZPjk6gv*wyvs5+haWTe2+dKvaU%o3>l!LzaXR_v+RZQe|wBgXr>-M%>L}G5Mc$c~; z+_Yug3cS&qZcauAD&4xNRqF9Fw_Ud@<{k{h{@!^7*u*>rAm+Qc+-_Z8)&jfD$cvY| ziPvx1;X2@E>#m23=1&>)I~)DYo3P&6O<^^|Sw|M%#P(h7>w9-%#ca!Q67)v9KGy!` z{{VIJwyn=z2d=GFrERJbqFjyX#V=brIdXDY(syN9ZK+#oi*2Gy1cnEJ9oztvP=X=E zkpUD51w=4G6cJ`z0OIa0_HDNGJe|7D+1%vr{FdkbFKYh)RXk3rx7+)4G0S>M_V)TZ zXKl9n=+a#X79x%hpr|}ZfU$x^JV1**2@n#{bPWc8Ut&ay^aA2vQYerB(5g>8K)De^ zASopTQ56Eu$a8!{63#$gdW10#q{xeb7G;4zIY|&zloQar%RqrRu4lQ}cOT8Pr~Iv> zJUq_FW!!%b@~`c>jMkfCxcfX_dR%v%W8RVrZcR!lJHA?S%i6?C$a3@TZ;>^ z=5(92VtT#f{#~oARc`~y-LtjrE&gAS-&R}x)XsZxTenE}@cTMz$Nl%L>G+T4ueQ|N z(Hz@-w{v_u<7(Vy=6K!T_b$`6PjBOPJ?($>E}}kvzW%}Tz4QM7%e?Kk{kBmZHTA#l zu6&;T?WVZ=Z(IDIYutBV3E6-8S8}esf2BXNJnjDg?*9NEu${e^Z10RNY8RRDP0NOLo?mhEe=FQS{GHb0)h7$e4vsX; zt5nXDr)h^fTf295`rm!mJ}tjfDeFx^OC=FXglbyO25^^wnf*m2jky6#bu^w?nZ->i zX<h-|OA=zTWd|_?_7Q0422l0Oqz(?|)bR ztMb;p&e_>Ff2(b;{{VFOB-y>K%l5BV?cVIWE3I(+R2=UPm*Wj<%f7v~vF!Y({{XXI z?`b{HkK9|ftSx_u@}2j>e3xT$UCzb!FZV?}Ppj-dxBSm$(!X(cJ8Lb>fBBL0U9a}f zg!wCfhvYWa&CZYc5_bOp^RKzbe_Lt#db?}vf89Tf>0gpWQXeD?{Zr*Xp?pWk*!R9Z z^|gJ~x7ydY@ID*nKdJn;#a!O*)4y*8xnga2WO?qkcXih5x9Pe3K3*lNsd*i(xBFHH zS=!T$4t=Ys8XD8OZ5*xORR-TuIxv_@=3H?F62h3CGxfkzSktIStffe2r=zPfSXD z2qfmJX@&#A%LkEpfWHF!2d<*4g-R^S7KEvf*tpUFTt0#e9VjyuAfp1Fh3Ax%IfwMZ znOK)3T**3Ds28^aiN>Uza3`z4B5Cz_hFnGJnq^6^K%>s5C9A2MRKKyyZLyD2TI$WE zTTWRTj7corIE&zQk(`W&fIPFvzQsiFtq7+l)U@IOPXTTiBs9*TRDXd-2`@5XS$4^n zZl@|<23oJyMsT@x?zEEXb2}WyXv|h$)EKdH#CXZ5qRs_dhZ9LmW?2&~rQ(J<8jsLp z@+^Z8P;c8S>{0d1xJL#V99v|Ts0)HoT?0SkEI}3lPVIWK&yeNtfxi`9t?8=Q;6USi|jbOKvx{FA5fhI zkXFjzhMe;bRgbvf16;gI1&CB@5U?sLMMXR#$hr{)Nu?fWsm&G~H7vaVr$W+@S=I9` z0H~*dC-jRh5Krlp93Z2nqfp@R0hfx!0detsieL^I@N^6@H4WD=URs8O(@@5^8^;{+ z2EQpNE6-F5^%&FXlqdk0m|S>IgH0wi2Z^LOM|IJBR|AisX@S0#Fj+7 zlGK8iKBB2Wob@lJ1;le}%pBvJ$aFzQL+VfI6MjJzUsBJ|pNiKqQ=kO6!c11)xxU-`o0t3C zTj6UbpVYOvr`?nKzAtNIdv@h!)t7Eu+$Cz7v)AoEs(i0-t-EsDdwU*xjxLY;aX!yy zV$R)rJ8io*Z2tglh~>9p>+YP@XEJ|v{15G)8T{S{Y5havwv_ifx9{JVxAs|%4dQ#9 zk7KjhZC|){)tuJYthL#=z0-qPGnVa}ZHKpWxl&N^&wjs~S8Mrx zT^w5ulD+etTN4>+pde3w-{?tvTdrO&17;~ z?P_9WK>H@zIpA{bUG2Zzp*4}8Yo1i7r7MuY<+}B9i;{DkRL}9Kl}XHP zY5xF{mU39D38>0Sjx}RY<{IE-*p*_d7&{)v6(?d=;ax@tRc)!R2EDgcRvt$!)+G?! zf`^7EisSaR$C$fw&qI|Z%T^A?oiPZ*+pNP}QIBd_OyqOe(^@r(CaUR<45Niw+l57h z71k=bu16+{>a3n&5p*ife8RZ>xLVCyY^XG=*Fj4^0Id5~3cdzXtgH@Ov3!iAnu=2} zj9incdwR2K)ZD1#YuQaS1B&FEr;Y{tt$3Sj12|BknQ-aoX60O$*xGeyu8x4ig(FGU zgYC^IK>?wOR@L2Gu6QM@K9gf&&5f&e_SfyV&xx&d&HNnsH}Rc2)~vGI zEmqp1)q)aac6q@hG$4RWG6E6+5djLQL?IA2PeC(#Nde{e*L(YmEXvtz%KZ$t{GHC_ z^D+Bi*~gaWZ^`w2J#oc9_jOP(M6u*^dJ#+!0P!V>5DWw{C;})SYjdf)c6TrT02^EB zh)l`d$Mplq>@LH(hjO!{I4-r?;izjAF@95B_1m?VA571Pfhb&qM}{$xXjnBQ1rNZW za!e9{C}gULfVhzm4kh%-GGJT-0h&p00&6%ZMb89LKo3%W0?{xC1tx)ndmkUa{F_SK zpYFDd?A>xlh1&OPV|h!3bs68@?(MeMbN`A+A$-|lxF=KkZp=UJaazPx{LHLs%i zKki%mv$fc-f@x-h6r>!h6|$8L<;TXUoY^bK_v zTpxfJ7G9xG#8qm*lZ<>x)*GbdC8%MD6LsV^w&mfOaKI|K1ZlaZVK;Kny++^i3RPCI zk)}b;Cv6sd=JTdPVck2 z+G^Yu;!>IHd{@oR*AN`kR zdH40~>#tXRIrA~UU&rP7N~kr{GwuHXv423_*jINy7ykg0YZtYeaJUn&#rGY1o16|!$Hd~`aoXm!4S1RK7uS$vWlOJ~1IRvgJ&QzZPGTaQP3zIJqQt4PT^#+p8BS18?3`s_`ARIKNq@M^5c@8{4IA^G; zxmbspPlCV&!V1Z9z^#k{T$U{kTWcAau~8hVk)k_m7_=~4IvS>6);6Df(>5>=&MoKe6sR`TY64(f(Bz*RN@x0ry<39Fk_jTXj>-?PONvRg2%tT5Z z%`yCD;7jHfF*YL^MW0X@?HS82hkmuyz?_{G_LSWn;+vTVne zU+3&D?tJD`6rnf%~{$I(1IzUNy*Z2Az()%>`c}w#y_vXjM+<8hE4)s zSCYu#F_GjIUYeID^O{14jqIJtRtN3g*(;J9np6VBA?akrZoJ06VcR+(-E5|s?nzLL zTwqT1qXoBO;~e>ZQFbn>8biH@2Sa%`!}WADX3TrG&IOK^n;*#I3dmjJK7B#UmeBhg zb5k~Pev{2qMSNzq*au4Xr!Qz$00~UQXCh4s)ZA)FnMIsl>Uzw4P-7DMEuySMrt-yP zmz4a+u}~tuKiBS(7;5N>8$3H7&>(-pCpg7WIBre=?dcCof55C80Oap z(upS`4gpcf`Hr#R@^`1PJ|t~ zc_+~peuspjT5j@fFa8m1`YPb}FbBTv3$!;$me3=U%8iJPV;1WXP8^X3_n|d7xB=CL zQ=GQ;KgZvO^iS;nu&DUezMG>foc0xvZa=?m8`am~?%fsp%hX~V1F|k6C6O*?;s%XZ zMNTvnIbz?{<*-19)iG^r%ilOmyJ(D2W=8gq=j913!rrOR-HD}!{c5B$vf{WsKeINN zacpYyKm-!%`FZMlbb+>8s8H2E&Bp=?VaJ z%02(`)w}w-(R}^;JP<=LLYFx=uxF%IApX?7v5vC}{*iom5ehs*eikg#*R)++(dWX4 z6z3@$KbCu@;PK;gCx1?q?2ZTiJRN-AZA$E|8vKPJz`<<3Lq)<&%AD~BMB=@I$!0(q z3+LiK=j`5(s9%&$Zy=%=I}%5U|!T8@a~KWFwZj z9_I*`OlYm96KOyktF&&9-kUBnZ#nwn&Vx@1J_F5AoZ0Trcozd7%*RqR4osEriP!f-gb*c6ovGCnu zrMOe5_OUC)AE;a-1&vMEHTrpq($pxZQH4Znei!t=*W~eS z$OWN1$wbu04&siny{Lfp7ObLOI_msFxPI2PBPc=`cK76uMEBh?b{~{tsl3B(^E$8J zUN9y0$(&TBv$17aRU92uHO(jG9siUV-`_33!;epA9}s8hM3R>gI9l?x@@ z?-iU#0Pu8NZ~TSpzqjN$H%peau)c`H%{=Sd5v-A*8Udy@^uX$YQbmfmh{*?yuVvR znK{x&YO6?*?D8!`jb7I~;uLmnsnHKItoXhL0CVNNipoqFPX=&W@y}C#$w6^S8+dxJ zr*X!hap!eKEq|FqdFb}6w|?;+nniTAdI&IFx=;FmxsJ3RecIfs9nOh=~D z7To~Cd)j^HLh6RE3=)o%B|5_T{G8giKaQX1?wzOri=*!_KjAXMW)}=i7>|Dh3U=#5 zH@mz42%0X6A7X4^Jsu%?_~D37kPNEtLWAVt27$7UCuhY@(e)xY&BC=a+Np68y))wi?*noh3Ds80Aj5L1mkb3D zD`jI}46E)CL1#YHwhEG;N!Do+1MCdPeQKUVYl5f&3zV`?kdZ=8lbi{!_K!NML*sgXwHn_e~AeMI3)u171* zbWh1GO9Y#N>*}A#g zz1JBlII{<4rzc2G0^L4gm;AT)5dhCaVc-Cl*zq5wndrN|2yeC-|3Wx9GwgoI4dw1o z=)J{Ro6+FP3(Bbm6`a!1^7$-O4Z#k41jNqz7JFte``ubcXl`K}mzSZ~Ycou?1>S7D zP;WTdVb|po^R!dcr|Ev&aE_1?jC*6sqrNa5rGEwIk*CAt?8ug!Wzef<@Gh%Y52c`nz)f3VhB4D^VM!#>e5YzEOM3 z!skH&=Q!zxnHT3GWpgI4Anz>`qXwLYEa*>CVoB${Af4~l(eU$=PhXeJ{;21h$_n)$ zPUPMj57pWKBe;(*EnPfjfgfWt-DCF3CSQaj<26`dKT$epa~oXq zKE%fTOV^IyQGg{5`a??9w9p!W{e>6D7Zp{!j{Vz`D0ER8lRO2580IR%uqP=~rmD11|B% z;G>%UVU?8eFT-dYoKJHhN6-$&dM1nQvJRdr5yRKB;D?WP=bLIdYug28HV`61EAWg- zSN^&MjunGRso%xQu3t(d--W6zdu(S2X z$G@lH7U}0XmEf|C3l>^BXHhT%flx5V#P_y%8%dBI@>!_#MfUzyO+z~2YRT6`r1$NyZ$?!Z{{708#n zTJy+02^t%Il`rNBfWdl>6{?C)8x=gR$S|&f$JlQa{Z`eL<#HQAby)o$e*9{s95v2F zrT_Ud#Z3@=loVCNn@h|#QX^XWR#87MH6ni_{wOsipv~XIBbKL5R+>aR8w0ohHeRN= z_=T?dDeHhSDA?U{d58CrMN`XxEyYWZR4^N~&-6F=7g13ROxS+~>-%onwWxPAuyfwq zcPG-xI2`Xkjpr<$|4O9myFp`Q6)DbxsD2`@rb;xWS@OI;G~WH0-?$Z_-^#r2MF1Gx z1HvxuegcCre)|YPpE8Zy-%ka>BUlXnl|&GZKpyo80_43fc& zI07EN)G&?PzJ`gnU0GiQ$bh?UM~-@TMRr`Sab3G%NIj$}Ea^Xx{BI2yKDdVFG++xA z*0{LK8@q6y|K)PgqlyIw?zbnp)yHM8BrfUdn;%quitaIG)bDiE8ZfM~;POyv34>ur9nm(6_qu3U#%_D$v!5`7nHQWx>(HTubP z>rwY0CxjLqze&}5KlLf8ri#c%ftdrDYwH<}#JVyUB?m~sIN{se+^HWVQ!X#J^k0l8 zn9jwI;5OWm>0?Y_@-Fi63ZO1b(W;aOeOykQCgcY%if~Q?9C2{kGXni>Zm-3;OTTG)@3eQ2b z8b-5;OEEObIxFr)$yXm0i!?yc2IJ`y4f(KvO>GjoopD&2evejr-{XW%dy4LaUgnI! zmrS#asCwejnu?c9ZvFMXA3V#x5}L$DijCxDIk9`u*F!0v*G;xOs5M69hyz~`{fc?* zQBTM-gj2|m*l6AW9n1PR26E*(=uZExcdU9E&u#2oWqh4P{D`o=D{<6d@(G_^+>@uA zdHQeLgft(-s*UCBi$9#&Cc4lGo-@$U3+H^Awx-E19vM(7Nw2}ph6Q*cX5#Q|=)ijDoU*7wD zlHRmBw-wg@9%4;mU>O?(LnzG zSN}wSp0oFvUN#)3<+vASK8R=Lle3R57^ZLc{jm|h^fs=FBKmLqkha%Yl&h0NB{onY zzoM$oe-ncaff=nFPjMA_UOU}N^z9sN%3f~cp$t6gJ79!_dsUNsdP7?iR7B0rBS&@BZRCO8;aAC`4EVZfCXLViO>t7Jw545 zde(p+txbKjx=hQR+A)4IpxgSa9uEb#_5lC-SS9_+xdFD@>hk9{gXR1MUJhLwzaDzl zidc@S!CUewV;_R=`D0^tU&n?I)kcaAdtIaDhaCzQF=C6-vzrY8%&ggHEaVC(zQWot zHQTH?XMA^KG*pc9Tpp~M%ribQAZoRT^6M=yt}g4ZdNf_z5vMPE>#_LPVUteUc)V>W z;2U_EWFaZJ=~kz0m^JL%IT)|!%)@ixY=B`Yk{dQq$4lmm^08RCknOp2<)pZ2mwhkMnLl$CSHs+=ZTs?y`@b3n#d%`eIvW# zd3IPU?^P`?gU!04(;JMWY+V}7>TEwbNU8T-roG0UtNZsEf&UTkN_rzv*j!_QBXpI4 zQ3mP4`p_EG{%T;#M|TZ6TmJls9Tuv{pF7amF+ZY1Yn`m#vbBF?i$3vsLMePZ0gd95 zSAf@gkK%m5o~GThY-|Wr=U(%k%fg^J8ZFnqad;haU339IXf-qCnZ!&UGkIja3c_h^UmkK=JK|51Mfg5;Vz4NJwc| z;nw;W{lsAR1GFeD90mm?By?r3VA`uSI630t5^{G#@c>adhOsD7@hAs?vK#waxVv-G zFj%slI{)AJaaseFR9Ht&&W2@K`go*^j;aN57q2eC{J1Es@PLa>sY5vR z-*`l_CYasd*43#EeGbNAMig}#hJALBh*Y;r<>*ID&m2Uf-uMs*wXn?_li$QvhzRIhg@*(m`CTl) z00~c(48rsDT(~wNr+6oU$7yw49JlRjB{GFi z-}w7sqtq$VDO$Dpob_bK#6pHCqtIDT&Difol|lZ)B7ZiwMZu|(-N(B=^i9KQ-^rm+ zfC@1jAC^9M-H7nGY8MGPZ#vbXGvF6t&Rf1hR(_=LE;Uv0Fv6ZaaMpG-RV0tMAJ~j~ z@hMt6J94&jGINx4!$65mTbNW-%%n2!l-^Iyh2*$^mnd$K^|_f(Bww_lpo7%t1EEg( z)(tXaZ;QyGJL$LB!^DPwxS& zOGenwQt||P>=N+*ZwX6I!&j+^xNzQXG&Urf5~~I{!0uWn<OH~wRr+VXn|sc|bVl<*=7yOP4VnU%3<8-za`DmUS}vsIf@A4XA{_j2RD0yE z2jW{X@m|C^&Vw4x_P&Q*?CkelJ@)|;jXCAgGL3J(zD{h4O#FG?!3H4%CHXm>-)^^;jc`lelNv?$Y{ti;UN>~CHYX8gvE3+BG(d+4?hX!Tg{dsvNAWCh4R7bxBUXXOaX$aL`(WC~1e9-CMnISe9E{)7+4 zXBkn>4YB2=dwG>x5Llo7O&4l+hLWakWwD{5_a^uCP1< zMe5J{sMs{?c=Fuw4`tfjZ9>4Hq^5q(Z++ZbSXVrIR%+>W8!Vpp!Ohxm3D z&1X^FtGt0&dCgZAL)jJIJJyVRsx>|gdMs-FvLz%QN>6Q2e_mcf^sQGz)T8YJp7?$g zeYZ=et@+pBaKJJ)90+%lx-S7+o;L@izTY+e?}J#|Pn1K|tI55Bx_huiv12 z@W;MpKT0<2m)aKLx>FYgbL-n5KUa+l?EP+!ec&0e$XSdhYIYuU?xxH(Ro5yf0z;>? zV=me9!=@+rHf%f;Gz=i^AT|f z%C`wb`~6@k7}O1jnYykKWYJ-4MjSzkWwG$hjY5TYt#kN- zx29XsiM}Mso}PRQ4S|Ki*wLtbG{$I?*B~OCHvZd3!C1=k9bVPj)l-N^=PG_@9d_}? z-f{8kuDL>S`!YOF%N2lApv3rN8mEWM5GKDTuc$uYHK47VN+kS7x(b5({PcZ+*F1JW zVRh|yh)jFmNW1KROdA0~S&||oFS(z;RErN2C!*8bURnz1p{ATh^z1L{wn^MCToCXZ&!g* z!yRtb^XRY1AGu6+t81&XMY{pOiLlNO7mGVWGo@FHoyo@Ke_!`MjCt(%tG%}CkCbh7 z7=%awQMTT?zJ}lJW6{z`EDH7}_~}oDBj@%T|G8r40G!=^=T@Ni z20rQV#&y$==qAR-S3saXcu}5Dn+B0azGAKV;!Ln9Z)^X{m`f1a5q(u1|3wZ)9uA=3Tz}4 zZ^9Z2Fgtu-xqWxg9G&~uZurJ?X7qgV5X6^c3(7^k4+nWDd*rtqo-61j`b&e%mfRCq zFD&xhY7Cg@cv-AMVdqr))?pLyj>GiUkS8v>mw4?W1cnZ=Uc|56hb7J~Gsds_4sM$! zKz$IiBhizdu&H0;DVeQG{|NGb^WeRUT*bYI7YZW#cdPzN*Pgt5vV32EMJ`NF`szD& zb`xW4Y2rG7{F8|KAUbz#=A2KaUfSf=fxB_!zH0)FnR6vlR53|kr@3%M`8hzZeSQ?; zH5oGTOdb0i_PH%*P!Tc2H#%c8Ro{Bq`x@PY5_9o{r|TY|^EQCguhZ>u~Y?EA3!6QzYy4dg!p zlPk7HJco(C6gd~sN8X4Wo#TT@s!9>XsWZTvnM?WO(?Bc|`pYg`E~}q*?5wZ(M&x1; z4+4Wb@>;A>aBBmeLIdK3V8a`--e@Wyx$Otz?pNLSoS5(BMtVOg>4FG#kl_3GOcJ!Y zY4WKS=oKGkah8~-pQd((X-3n0E10t0V=|5^QsAuh3S`Mal z)K$jFas+5cUMO~tyw0HpsspA=rAka0kwmX+C8S=ut2!J@;4D7|c59`}6rc53_uwys;*?dqBW1Tbdl2c{gcd`<-^??9J4lIh`WgSs5F>-Q z-aPLa_dzj1t`TW6gdahm?eYCSg}y@LXk_IL(-00hnO*PXH%D1TjHH9$xjD5*qK6cDjEeUFAqmqG*(sF zY!|XhFOJ?X>W=ox;>nA@W*X3u9{3xn2l>vRIPPWdai~>;)W=T-38j|{?_^a>$h%H9 z1md3Qxsi;EBMvx8o#Yh91Jju|484YINs1$l*`lj<`U1^*HOMl;Gvl>BSOEnT@dvv}(3$N@RR1S)!2rL(+*N>@C^bucg zfIL8GTaV|4P9eq0!?Y3f`&8`e(7cc7un^q~hmOIpv$QzEOWk6ZVonrxj z@obQ_zHzSmF#1*ATr zs4JnUmduTs(hYycoU9f#UY|fRRnBj+&`2=*(SPz-Y+A3qjFDM$u0hpgfS$2;9$AC* z4*{FAbr>;y?j6z*UxZN#njkD$@lyFzNVbN2zI$yEPyPj8%G=RlcOY=o+_}Th|1bi| zW(QitnW}omg55d2HlEuwx>#V0D+)xx7#MzS-Tbp>swr~R^-_sPdAo)!q}j4^hkS7d z5LcGgyNHXgqAd(OQ~s%URT$+WBg;U5k6O zVA99K?8yrzW4{90MZxgunx=qPup@|@jSSxlT0gJvm^6KL8z3s+b=GT$w_vM@Xc&)n zigU2O;xLZS4FHJlOw4|b%MwP4eTLL+yWEZR%>E;wZSok%GdFW$jkDN>zfQ(jl!dm$xDB_i;84>CMx|Lpm$;s|>>NNm%6dU{8UCy_8h%j?%fwo!+a@HM6Uo zyILIkI3W6x(bqyNAEy%^+i6TydsorSVtqSk+O_T6l%5^-IAi*fA_sb+)D^Mq*NZ1x zh5@sN!5x*7#i{18`R`9bm%evu%p~gQ8T6M}F;1#4${ea+lCeLJygvB17hb4q1S9Bc zsX?GR;ph#>v5c z&aj$P`&&a_;rRHvE4=U@L9|aA*^l){zkAIcFzv9%IWCI1d!I4QcFv34%&Cv@#l8B% zH!}HQ=92^Z;rDsYQ7Cj5qHlI>^Gwz<_GgZS#J0NHP~!YURYG!|&${0Y-^h6hk?)e5 z*?R@B6rn7)0+cqe3SldEf1%R;zjl2lu@^8*XBb@CDI_X%=A7~b9N^Ku?9;s5_k~ zRMt9gPm6|zY~hOP6ZV>Y1$%p)-j0?nfZvX+j4Yt?Q&FH=YZaNhf#2{xUW}AS%E~BS z2F2Lcud?SJguH>yU)g<%c(#JO5Dtk*yIUAwQ?NGvx&D13kT-WJra+m;S)-O&2B>xN zg+MOB#T~60%`boyk8w84@Wu{mk*oj=I%-l4g*J(@BJKaA{ZLe80_jJ5;Q3lTDd(&6 zIKfnRgPyO5>7x#-ee32qU-=a89!*`Nxm>gCW_+DVZMEj4ht`NXs`&>&wXT`TNa{<2 z;l+<4SyA+j!s&yFBnOQ9?ta@Gw!aGXq_1*)A2l zu(dcE5a581H9Zcn;Ejd<) z4GMon%H&Pv9aPH#Qz#R3nAeBuJ~pb!{i-YhWXnf8GTw4}cK`QicdWT&y+F5OV3^QOpTAoPs_vLzr_GU3Ha z{dgo(lvAI{gvV%`})^Aq{2D3|SfpILB905U5lHXazU+OUwe~>=g(}i+$ zIURZf_dvZ>^fLe!voDSp`F6%mPf9ShZG1lWQTD$0t#!Kz?LZe zSmELOWt1W;#5VW2mvu{WLz_=StM<1bz=W(yYGi(U%IsJ6CGjGo@93Yp`(S^6Oxgja z-D|ZC--jxc^}7%`YPAy^zj9+H%%_YBfa%%$Dilqpr~TU8Z63{tG2g|EdW56Gx9=Mr z&}kq;i$|-R+@8MZfgX0&7>(|wq;MJT6Dsx;$=GG;g|0E+Sn z*>9w38h9C86^G?HYb+ksdwtkYF@D28v;45s{e+cCErokayGJ7-oWt2m;H`%`*$y>; z+9k4<{+`3bgSbVD=f@9b2u;>GkslxZ5n{`ucLFAK`f{&F4jkBNvhGO8-ije~*NCYj zfLum1`W$y@4wgwiYecI^m9ad;Wc}CUSVHQ51T+cWd=2E8a&e=a(Z$G8c@YavJSM}= zSq=U0Cy4%@6oOwsJ*Kdcm-qZp*jr-J=KY(eBN$A;L&!FMYK#gWE8d|TZ*Lc1AnP(4 zg9ez(G@=dGX7@K9pVsZIA)?pNY7?^Zk02^webGZyWc6v)Nmb{HH9X!b-K;bEth7#e zasvgEXmyl7c>Xy>0qftsn!_Ept>x%4%tra9)5@sz#73$yI=`3=iLxk&B8j!~#N)X9obCZizf4HIUlT~0Oo~K!6zEkR z%hq>&g=iU7RsiT+Y0Qa73RQ;=ryO#N!O;EW<)1RPZ&^dyuj{v1KyZ(2Ufy!qf+gRr zZ;0dVE|JQ|UKTa}@da(#PVvJqy8t0I54#*zy}1zzg?D+o_wX@7*I^T_Y2iP&ZgO*8 zIVDyJDe8y8?d%X;yaNlZ156hrVb43ouptf6 zR*5|!ZW6hRciK6p9e$&axAR$7w!TWj7^q4YFXkJ15XCPWXHNMGf(pQpoppUCMspZ& z($h{VSIDK~B7kpuw1KkWI=))lvUk10Z87BY)}z+0qa>%OLYs*uKn7&HbRfjU_zhmM zg45&QySi=>?=8@?Cvq27=Pj2#ecM;IDDW*R`^v;Z`<51lx)aWqUSzOpIMxssD>#)- zJIHu!Q1u@g6+V;3@wC}6?g0&T12si_d%6NjL3?=U;Jk6wFPLp!`!OeHI+qNv!T9(= zQ>RtM_g`SJO-S^AY}nn9=*YqNY9XV0Z%KFX@rQbbpV`^*4d|!--3-_UKf6x0WMA1k zN2FtG2h`ks+h;o$?ojKHHkuvbAHJ$Hi&6~4cbOM+j-K$RIPdrb=ghJqILTWYw46xK zHY_$sNk7$=5E|D8N6_|Jx95*k|K>-;BT%@W9uydMD@t_W4cVQlD#|gR<$M8ybS*#z z=9e%hhXePb2u{PzVeZ>Ko|LJFK}mo0H{Cq37^i{q-3xyls%QB`p9zs3+M8N~*ltQb zYPw+!e56QtGFAnkGEUYhHtzr6Qjk!@R1#j+W-qHm$^Jh9E=63<<%RrHt}blzu)Zb*?atP_^2+RJ%k0=xU$sa5w@V1AWDoG zZc1rA2?aZ4%%rcT#uQmy-jn6BbVzyiFq4f{_3w8lp@}+55N)72M?7MbI$=&VRUpYM zGxVim-!uHtn}W#rGzL)oiNQ_In4hmd1Ax;|abEVk2wN!ZUGVJrwq8e4Xxnra5gqXi zBa9vXM}Wf0d6=iGyg{{~KxP$WMr4X75tp$1i+wNwp6f z%_=dEYh>h8 zMLvkqs;ITL4SErIdkgzYAO868w=GFTQsx?;tNns<2Who^PRPsZjpHVBNHi&gwte`0 z$W=|B*idv>*vOZVm5EC4dP;<+y9mGBcxMY6hw3^rCR7!FX4ZE#0>tU$Im!i1 zaCS<=L|U!`ggQ}v4GOl9+T63J%L;zCAOE%^$FNOzo9&GoK$Wp8EyH7<+%uI~I9wDS zu>m`Pp?}`i9MGS>KvZRChxsL+9IbaZO)fXy^NR2fR#7;)2o0DBV5=Cfa6UqfFR=T% zhwe5`8X|WrPqst~iSL&OIg;)e8K-nTQ6v(grzdZ<5hBiO% zDCRNg$_c|?Tm-Q~CcK)&Cmi~G`YRt9?AvN3rB(U8MfjLb((0u155|y~pifU8+9;6$ zYDj!6{V3nuO!uT(-TKKXq2HqGf9_noTSIoNT_DpsljS77)OWhp=fH*aReV!BoxgYL z$_Rc^Xpa4pWWY=ef-o_AvX@-mYGp54W>)iPJ(>{%#}`Ac`=uwBXFK=2f$lq}5XW7i z;X0DZHXV`hXewHNtKG=Fc_07l@Pm0XgN94bDQicN$#E}l`<>qWO#9^>#u`*B5E#~| ztE;5ko7)+1trw>q8;ogXVR3uz-_UlI4@7pYGR4rxWW}r{HT&0J8}@&A^XfUltoWh& zm{7Z{2ufC_-C3S(C&|XqrDD(r44b|vZ1IQQ9|`SqKZjX={A2suiFWHB!B_vpphARiM>XA$-qqY?$dpw^L>C|7tj1Le}oR}yFvE}?L9P#v-I{bq+8ySFr*`D;jw-FiA1)_lB$}~Ds}GFeH2I1us|?GL@tXdP(Sis znxYc5`qR>Io^l3udmk9pr~7kH}m@wyx)w zwF!p86Lj4d*0~XEN4l)M>xW00N0w)}6hNEetL#M~4yeo26q|Mg?!p-lZp<0j=x=sm zQJ4$UeaMAE?Rk4W{!5gWHS43L_nh7b5vE60J1f?kO}~&|r1=yWxKdF19`uwc!sTh) z@uO}C2h}21PIV<{M*8prasdfqLIQ}@RC&sZ1}C8|pjtip1G9k;MWqA3^oO_08T`y9 z59VJz8!980T;LP<_^$tDEXSlbc^bka+Uj#OshH7ZX_-3x$h>5KHcwW~$E4=xzh3h6 zbf+jGxE)?+o!l#Rzfar=mtA%OOG!HQfEK5dq^)8FqWUQF&LPX$6wZ9p+qDh$d()cQ zbw&vb#$*mTHugvpX>=*dpRzox7b*AFVeK@DY^fKgW8HZ=;O^nAmQ@)eofp@YTM71t zz3;XvlVUx`o%HIsdz!D)Wej7$D=o=XC0Fz;PgKqq4T|9x(o4zZm01XAXWGw}Tm6Pl zU95|J;_3}Pf`}Dw^ephz>chw7>DZAKS$pz7S#LK3w*EUujMlaGmy@W%8v=VvsPtaQ=PQl=1)nG= zZ<83K*_3W~eWD{9H-jVG2PNtcCg12~roB?7*7PR2*Y}D;KJ0D+z0`}$bIt#0i^Db; z+_a-waQ=51&`x*%BJS)S{Ik`^Z5FeA1iL%%K9xJ2JSeWJn_C^tc*>&iCX##<=N%^b z?1Gv4jb^h|B!p3v$BK}TzJ!_`Iec_|-j}@T!U|B>o$=21Mk)^eXFA&b_;e9pxyV zJPNqYSn6DLEQF;JSKMcL;q`YJ-1v8@Y9jb?6?Ji=S}{7A>5HPu<1Be|u}sBK)rSSV z{ihc&ddy50p5jk+P(tA!t6oYJ2OHzxua2^C>@&khwgq+=&j|XayF_n!Z;$P^6y@nV zR%^T7)IyvL4i}OY$Yl#Y_Yz_v=i-M9r2s+Wcsc3EHk zs-MpSUzgozHx|Bv(Pq+{kyF#1G0l7saiS+0P2I5&m=}yOz-Pmnyi%<+=)@<&Ae@}; zT+aHg0#v?1+VHAJALE_ImJqaN8Fh5WV zm3JSk8S0PQJYRCmUM1ww(AibC^Vn3-fq^4>CQvZ5a5psmWWRWJeT`IT>Or`M?A|%~ zY+WN7KQFbIVY;REgEAA=f~AlnPFKxtIZ2tbgwJPR{_*Bk?{IAr2f^(AtR?vQor*3W zPJ4giA8}t%(sIxOlBW%-+6HlQ*2ctU?9f)y>KX$DT{cZ*-Y@%~;{(ZtNeXc%^7r<6;zDr$?O4nZ@*LK%^f5N7!FTnmHphf)t@v33Ga$C)y zc&aJoT&X(%p$A5uudc}wkE-T*1u;;iO_iJpsf~z^iN}57O|9cCFzLOEz97;u(dvG| z;Xqw|pGKA3Dx6zwLDN(Q56^rtk^3WJdSCU6$-NF@kFzzg`TGoi)2#F$^I6MVE&}2w z#QH~%$lYk~xoa0d`4&_iga))+ULKQ9=Vk|r&Q~cB3t>n=A zT_b9k0?LFLP=1hXjG|TVMgY?vxCl^}MU$P0_-{|5q{2kLXPj+0Yd#=;SNe`|^kI7s zs=YxM6fE^r7_S z5h+DTO-)1khJ=LgL1~&eH>=Wc{Br0&f=T}se0|DOo#1=}!M^1SOUTD6hM&vAMOGF! zGu^0jPjts$Hl56~{&Qsy2kgZY!&7FT;bM=(_XmPIM=##Hxa%;6M*di*-4GC$*O1UJ zuZ)Py{4+p)N^DU|`C6Gk)?WF&c!eCfhvo4D!yh$+5(2^HMi0o^I_ZG(SL6Vs@s|%8 z`!;W#v}2rK>6#kK=ea|N#EIn~_X=*`y0c1fKxFcoh_*Kj9wolcX3vuAs{~ZOlG-Ad zx2NUJuZ$dzn$?0Ce6LB{(tp6qk!zA|2ieUom3JYz(K_SZ0BEY+uMr(9CTtlEF{_B< z0SyI&OSt+(1c8O+2o#4M%Uc zd*QW09=tYKWw(EJIzBimIURs%ocx&*VybHmHu14^+Y3ZBvu2lQLF9yWehWOW>)(bl zL0Pj;IomR`>Wy9*etDk!oNhUpn~;baM#!J?yqfxA7X{+oKkD`wqgcS^}* z4rw~5uESc1*t`ZnvXCX|xp`r7WY@~^}XJEpe2 zS(R(Qx?4WSN~Z~U4;l}7uzcTy2f%yq%Nurlo1o>hu)>amMOWNPI>$kA!% ziE`xmVtwQxiiY&qb^JFzM$z$m-}!;Cspk37vi4U$nDBp>L#ItI&O`j`X_ZpOz5VLUw)5*O{)26MW+bxgxEW=2)+0l`qfmdszX^>M zDM$Ih{t-AuxcDy^h|DpJE^V%W>z6~DcvLid$TgR%o|QV95+P?wy>g2?@jef7#Mp<7 zy^yC&n2^Ie!2)>1@Ufy_-9F?!&Ia1iq~G3Zg*F%Q3g@EilM%i8Y zmrmceEQQlNz$<15PdzFAji5s0a2p0C8v#33GNX7r9@6D~iXHW7xTDJJ^MH&j{93*& zPd-)7!@@=W5j-!J_zySQ?rFRHBJi$mp{Yk@0e>M8SAt|iClsq!v~dXc05(8*ZnLfW z+;u(`*u}$onnqxky&m1z>(q_T>u0Ycx?=GT7MH%IPLw}Bt+o_~>$^MHpXt7`u3T`$ zOB`Co`8U(ot5?b!SNUNp3*&rBtcnoe9hHnqGmNLBMZfXCq<2X$R@vXum2Ra8RrPEu zR?8K^PhOhqgbQVOh{e+De@r^==lhjEyu*(yDj{3Ld3{0o!zC`Rn6%y~PFUT%v?EV| zVj$7N-H|+}Ne1A*#UmmYTf&%5{pnF0+`wP;PgHJG!yW9z?3^vSXqyF9V-vEQc33W5 zt)9ZLv;94dh5$qL>2CKozg4e-LfRk?89~I$KmatgoPPFaja-aLcR=5KOyov_n zz@9RG*EupiYknKZTf6bXItq`xau=X_i@wuXOC{glxL&Nt+ig*Q0|wc-5(-BLv$(w2 z8AF2+iAQxs9Q@N#2K{a?y{*>&b4t_!GCmw|>7f1MkwvDj?UpKDEc5u4gRgz`o7vpw z&QOVrA^PduhL?*J_7+oIIn-7{FE*V2ZW*<27WwkZPQG<%FiYMTS%MrB>c6}D0! z+*vQ97v?)_K1hBoSmDfachR`FNkn1hyJ5C2`+pprbzGC}+r|e7DAL`ibayvOcXtaD zaHFK8MM1i|rCT~iNjFFe8{M(dIpBHsd;c06e0J|1_jaAvd7Q`h*ogw3h;M3Ft5Ml| zbFjvIB)l$P>?n}Qf6#VAfol9|DqKBV4pC?b>o8f_dzlpkw0rGedk`FgFx(3SUUwfq zi%xh$V+XshQC`ulGT3ELkGJ)CaJ_5K)v3~Mmo{sw#BnQh?szTR<=%MWukj0YBA4<& z-#lZRwGxewja!`82WX1)h2oB!+UR9RM4k^Jf1Py|Tm~w0>@;O(3X;p-@OFQ^Vmr`e zwDHGpJ+hv05?ln zYD}G)jLZaQkG6AMVu7LxJ*O_`@vQrisa*_jn!w)B=jx8l=P)VG)3*o(Bet|wjz2(Vbjp| zCF0vaPk!!q`BOi}D7-i${W~Wf$btpftB(iTH0EXg#njE9DU>R-E%BLXv81w)@nfj6 zOY=tk2e^v%%LCONx=a2Cp!?!#c9(PeBwYUVdb|3mC2wz`-dV~uDhOUhg#d9d5M*B) z&!VK`k2M8$*l*DY4bTA}K9CU+5{8oDs!3QbBujnrNnAvv$*`1pH=WvV^fcT&uTFc& z*gbwxh6aCH(RPIeIG&Udg#(way{8g4_LpduNR|K-zpXyz*nQ3OPOd9oDL*@S-RtoP zsMtT^AmigO`j#9oN0nOsHweqn`AVn^*YCTGE1Dr@)H!9@q8zWrxXg?DH0z%nTAgUh zB)YwfXB_zWC@ic?oYces+cn@vh|r^AEoE}q9^p0iV2p-Tl%TjY#*u=dkT4oO8m4ML zkH)W@C_qj$!$^*HoepkcSbiT?NFRF{de%eOhJIBopYCL9OI?egbbiELS>Um!pJJN& zEq(Cd4FrO`DR~x14w$Z5H}Zzk1a_r(`FJYR-L$&Cy0~_C+AKq$^yc$8o_)X7|8Efr zzhZKSmmjFI?CiFz4;`4WX0c^bajgSD3y{iOmOG10Geh84%8}mj4ESca%}QCD8mNn z#1<-3^XF*F2)=SMmu`f6m8-5^G)5A1SALj{wlc+2xUl3)Igoty$QkUcl#>z`g(Mrx zD6%>gXx}3mc1cOY9S7ZMN8=6S8~Mg28>erm!T5f4DVm&Gt?)4S_s$lpl_xYuv%7K# zP0<~vYMHOM90B8iSVunV@P_J&i;b?ynw>L=I!7>Q&%2fJNepX6YeD9vVr?@-FopUV z$5vBosacZ^AI9vP{>!1aCFQTme*iqvKa*5?!mOVMVyN7@s#M`TWfHo*)Y{@3*w%MR zsOY>;apMNHfw=494Mk<%tN(@#+%4@*rX!*4E@{f-yT990zO<6HFXUS8{Fa2K8RkkK z`=ae5o`J3psQZy#RY#*ZOi$18Yg7=xSxG}%J{JH> zx#r6VZGR>BE`vIepA9&I7s%(Vt8u}OF?6q4)#Ag2IMKUVN2p7BDs!isdl<8dz>+nA ztUm2f^E64blmp+Vs=)5+)!chcC~w3=&4I`6(k}e!-aUYK8u+Dqa}c*&tUGi^>&Nwm zDw;ceNPd((waxJxo%#zdv2$jE`L<9;i!yoBqp+7&03S(u_=z>%WO{J=mcJ3KR<(?M zL2$28ay35iQMa`X+GvTN1&T}Ez6lKGMJ!zhOt&^Dfx0Y`we*F)1Rw#_=rO8tDw3TsPG{a29b+xW91le=7&3i z`U%=AZO+i#Ux~#PH<)q}vh`z*$j$0)i$+Kj{QVD5699Z9h=aE5(m?WiJhT5WkUe># zd?ncz5D{S!lc(G6mrnwNuPAqV@LZ@B&^?@}+WUm21^0s#*7~<^eNYHfdML${Hu|yP zNACU|+13CE0@-s1@d@5s23cJAFz)XjIU{R*Bx^mrW}$*9Ggm?QUhmbz(Wbc+o7uZR zRvDBNR`s|vZJ=+%NmwQ&N;7q7lw zg)f*27n=?^+y@U6UmRL}FKH^0cqFR*q(%%M8~^d2eO2eI}H(vtFoBwf$m# z!oWztbaOI6Rs4H>`FZAJ+|p2hH!8Ui5?H#*(>30{lLnl>WtgEjXRU`pT8(6`m1!fr zlY7P{J?=)rQ$Gzg^^1H()v(tnRPWubB!uUf;^CxH>LAMIipSxk)g05^Kp`vU>kpQ% zs&+?>0|HC;vPw=9$M;m)Qdy+Wd6+nv`g`1G+j^-{!z^an?=bcA0IM?!dV z8p7fm1xtgfTI+%@>=DL)q;|w!51+rPO^)3Bs;Hia(f{hD>ARo#Ax6aW#oyc`j7%B= zcGDCU(+k})Hi+`9BPD=WAteJTq7x0%nZnd^6!R2G1C8JG8Qj=EqKXE?Lg84qUL5Kgy{Ch{3E1SOc>EIIBfbBuazTT96V8J zNUMJ!*&=M$q8ESTOVU5_yYB}%4?nM_F58ZrN!M$$r8iTr+R+M(&q*6b*f|QM1h9t1 zaXc5$z8>kPeQ#hMl5603{jQkYGdx7IVoThB>Gy7b2)W|bJ;eco9)?~#dW=&XZp8*B zoq9M%I4K8fB-Z>?qJHa28?5V@R#Rx`uw(@S~3NP{(A>VI6 zdVxxl{}@P?muS<^1Mz9{*zd%blQl;Fl%u>{AwOUKj4J;b5=r##2QA(7U1Mek9r)<< zpR!epT@^Q>nY=eDziBP2MIFw81#S&NaH;-KRC zP;~-@Uv+zmq%&>y_h=r1MDO(;(i4>I!mW2JYA`4)8rWcgo-e<9D87eg_VWV6@c3L_ z4eU6{DPrjzY)5H$U{&yW2m|1A6&`s!Vo`SA-oRw}ZXPJuBj|k3vB3On2Sx!F0a99^ zIH=FpT~JF)tvg#^BWzn-CVnGG+6)ET-kCKN9PNoYTwMwackQ_i%3OusA;#9@;fwIg z(~gAuAo;*0@H+RnBps@&3cfipeJ1aq6)~kEiB@M#eKNQaXMy@2KL8zCx)c93>t_zz ziNiIHHa_csSI~QB%axKu((_nAhT`0Mq$^;m4E4~88El-NZ>`M8UKU0X;wU8IpsDk# ze8+rg=FId;P@RNJA0t7ZA3^4(v=cM=N+9-^m%Px99(n}KPSF6b&DOxq%(3f2r*v`> zU)Dod>AO54DklwG_HwITKGG$`)N*mJ+)%4!@lkif)7&E<(*SI)Wbb^HTwm=b4!#f* zC21b(P2;Ln{NXWRxwcCT9Y*n~cuM#c1sYw%dbu5urBU(kMHSO#flt~e9-MYHhP#FR zod-$%ZzOJ;$>@5{RYVQ+3qiujz?VJvUe9u~=@TfuLTrWvq(#+Gq-m$MQmaQ{v0Kf8 zXGy%d=`7cezrrIX$ZdG}V+N)8I3JP5aI8wC(t=nHKeXleFw@!~rh22dCw_dp@`6?Ny%|Q@s3R+f3%r z=l!I|oMf<#TUMWCPJ!;hrAX_{RRlcc?;cnbG#93g+U1wV|23R5%yTS0x-nm$TscAf z2k02M+wdll{t6qlqATUjk9JWGDI%HsImRJk0C-3cW|VOR7L21M0TP6zF$D!Dk_)I4 zlV9EA%;Tw_Q}O8+&?Myx3exehV+3txor~=iy#Xs}i5HAednu=1oWwUYc9^U`#T7g^cbylxW*vb5jl!q#>K_7evYQTr7oUq?3_mPds*?@~6VWP{UQ zrY@t63ingT3W%+aPK6ia1-`u&MAR+5sO`(?U3!-ub%1jcpk^>zB_|o!8rc4O6O|P4 zsBLTySy{7gUV5q{>AkGAt8KRxOf#Wb@~0IK1c@F$cq0S35;x%i@$N0Z0>^pXmm7Cc z1(Ic~hvVKwr~lWS#-@wMykvo?tb46>S&swW9VEy)w2u8|*H=9Fq;?z?OEH*35m4uv z)P5xaUFQ_(afBYB#s*fV&C~t^l%g^qC;tP0yK2Y3$}R+^b(`dqZ$y0gni(B0LfVP?|cuPNDt!qh(86uEi zOmc1W)^ZK;GzCc)S>Z&jk?zNmv84(42R=$v4h5O0>_0{ zf?DddjGT*V8KLhbGG*2+hlbs~))G+bPUVKpVV4+97IVFX9)%hT!fBWv*wg-FuTcAi zyu)LjlY>926DJSh>xM1xt6j8$Wo0g(Fx){49Amg((p1?&)h_O0Atu-5M5>)((bV=) zDC%RmLx=VbcQ?OE+#6?deBlsn%AW%y=_waN*<+asGq1%*4Gi5f<&|S(8_1N6w=h!L zzEYc>y{ctbfx3X0s{B=%^W*{}u4Mi|Zjr|+sOvHw?7O%Px4G;lsBf*mgXez#wd;&3 znqWuMO4_o_Q|sxOD>%gzPo^dGYHnM{`Ne4H@ePp!+gt z9p(+qpKi8GchU7Q%NNEg&Lwew`J}xMfk0ri-kPI=E*3G>GcZcXliuWt{x=hfx0|aKL zGsV0vuzABjl$K)!>lLf&_1I|v%KsW?Xrv5N&fw{*VFmn5CzE5j;Bu=~G?`Mft2+u& z3#L|X04kXneUF7 zqWY87r##^u43!O2G!OKh`gpdu~j-L7yO5~yteSt-z z_miZ2CHkE2#`tW&n(F+B;;5?;-DZMP^fFc!5?_~*1;&}5^OFIIwG5$&stO!UrP9jN za;CVKFVn;n`N?8G8AOr9xUrGe7UJnf53~alezLd?j1S?-`8#6^JK`AYeY=vHF5CJR z%YL@SMP2Nojh1pw)L)S5%d)hu`)w=H#Nd+TX|lwz z_X-rcS*y8Ej~XI;Oy$~Z#CIgC-mQOcn!Bd9TE1g6&#laaYV$k2(E+_P1xv`?JnyFE-ZjZB6$-A$TfwLThdP-`tu!R9tVAc$O2=OgE-6Y4A0@o|`n1*Hd_Yc3oA$>r3fJ z7x6gt(_-~nsW>cnc3nU8`_b9!TZTJmwA&?+9{#+5T0OwL(cGrA{^^=H%eP!d|Fb)O z?$G3Lahi~=5SVL5s3F_hE^wLM!re736s@pQM~C#r;?yglk*e>yjXT@akJ1_grS*N4gq7EuBWus-qDz^uHZO=A1Iyu_nKi58NYis{&^5?0SDIRFmONV zx8IzedZW%Z%h;@)K_@?PJ$zsF?u(z__0aA4(aM*64AtE&TYd>~(HnL_rY^vIOlDGE zfeQ~Nr%sw)uVc{QTjnK%Xg4MbW~or1PHN$*|ADoL@_~-mM3+`N--rmNU;J z##&8wKGrd?U3#S#*{M5x0;^_`9qu@PkDb=z-t$$S1KIOQXXN=LDsacxeXrb>z1TNjam^t^VB-)!ms_ zy-wbLg9IVn;=+dVA;ZB70OPb@a9Asi8PnX6Vw%;Wk$q;eh&s{BWDZMK8Q@&7gYJ2!vFEYV#pSqgCSntHn(@)_U{nTWYNR%^_&tf| z==Z+VEiX60JWd&haXLz=N2Z2SHA)OcerLRWB?d*>)e1tDhsyaO?nldd9KVO8Uz2?^MGLO* z+Qq=h5_b#ZW^_5-kJ;Q0>qiOT9c{Iwn8Xsd& zyd?Pd9z}Y14ju!a+S_$-m8wN#oefoy%vBF0PP;5!aHp#&IlQVc)k))w3@^5GUa*Hk#`%T^?oV|lel`7Ko!}Hg zv%TIBD#G_d|6b4arQkyh8r#0uH`6^qw)Rj5G&(Gb6`Vx5m#9?C!n$|Ge{8e1boHdw zQRpup6Y#GKml!j8QzboEmmVS>dhl*O&r}V>y5|6x1 zXMStCBw!)ub>%P(cyB#`k+D=r?z!>8!b#x|fPmwL@JLh#rVw_>SqAPu3WXvIvGh{K zSiYCksZyBlhPoXFUQaY|_@;eT!RSkMNQwDml^M!!#>+QS{)C?QJ){Bo!darosAIe$ zREuxKFmB(e;{;}`g}oZU>s`0fQnue>F=rr?jWXeUT^JkMxbT+>GK09CZZ`#mLS6k6 zmyPK~U0^mlSD#lk)nsHIwz}h_Iv~WX#GUJHV|Hm6OaL9dC^?n!w*JnlsGZN--Iu)X z@_!%@B~$9G%|Y0yf|nFS5~8c6BNuVorYcR4dLML4Cj3RYE-YR|?DE1+sxA>hzqdPk z;0Lb-QNy|*Wq)#Xoq@a(C{3a*Z-BE?-{!BMm6%2c8Mb6hJTw+TFn1BvTg+rc29Q5 z=iFN;Y-v{dKcOiZ_FEm3d zn2zjJ1>#IG1oaX`^1@h{x|Y%Eyc7lFzOZAY!>lgjte7%rD<86MbA*LLc5cJk`g_OQ z$`_Vs!5bahcp<9+g1m0y}yGtL`O{3G3F8RG$ zEu(mJ|Fq4csPTRsbe6}90?-S|*3Tlp#p>X6|LWc*?z*l@wdHvutCV|!hmdic-Nev_ zWYH?U&8N(B)E)1qNsC}tpe&IX`7!T~{jIR93s5Q!XbBdyy3vM3BhAH& zi#KmuWcFme8JaB`6_TT2%l=uci2fUwG$r30`aOYR#Nbo*&2CDfe`3E*xU4tt$um!| z2floG0gAhdooqJKv@Cmg1XKG`ocE5V1;@;`;1z~UDYWVu{R-1D7*Sbi6>?B=oYS!= zQd?ZEb5zn->Y9m~EQLD81lHK=$KM+j0(}}?oRKLG%rQ%>^}?kQ^oYKW8pgWz3rQL< z53I$Qq%L^!#(VCbW|rLHymeEm8t!(o3iS{6stxxM$*ONP89Z>~%|EHp_q=ss&!PDb zu#BWa^#YtHEARP{#)os(z@&ivOW*Z>H&5#{-HReV`~$L%-cZJj?U`z#&vnU z<5>Do67|*59F9G4+e5Zey6ymjqMR)??f%x$(o%ZIwOn<)GIN25xzZqdjp@1#?(1Cz zZwOC#R*XDXy@Xiv^^3C~VS>3V0#UdNHG+FlrFNOm=#^ClhdV%e{`j|=W3`!%MAa4@ zQw;X4=jQpt7t$@;2Igt4TVKvf8NLZSOj^2Vs(Ex^=B_t@7HA{scq0&?Q%StIEtn{m zmRP0WjLPL&I$k5-?$5sZ`CZ_&658bhw#`KW-Z3z<2S3h?*Q?AsSW$9(d?@w<_Mz{ z&bMEzl+h|BHClfJ>^U;V*?;cjUwBBWai8+_qaghcpg4jj4w`7%(wnjHFCy*!6FnY{ z8u;^*_~Fyd@Eg)|k5{|0?0j5$TT(?HR8ijwUR;HEGu1RRCH-aKd}2U(M50@Jx?$Em zJ~!~z=fQlVKxc7}wQgDKa~BhppIa-y_^13G+06hWIO<)}clYOtH9c+#Ah@+ovK;nz zb!z>xT(3+8I~N@mY~4xg_NZxrwH(jamsnY%9c{^?*LVe6;3FJZcAHd1s$Ns~R(j05 zb&8V;aS5|-v*XA$&H3~gWu{sw+sm0KUbgVsT3-M*&1kfFgrT(kD-XM|b8YC`f>2=N`R z!n@HHGXd3}FFLWQF1RcK4Hp{B(PjU-}YXby%mCS z1y{61y*M2gr28?mg~Bf}RJ+88H@6&%GH9de#OM(HZO?R2voFdyZP^V65qYKFi>6WY zbu<3-_m&Q|?#gxbu+n;2=@S{Pr&dtSnM_#Yy*fB5Uo?NI0jAsZT4sEG)#GxB~HV_VSP0hB<@b`fij zanqxyKGw)4%C+g|W108)A*hESW-{OzY|Z(zHggxttsfW1qnwAeWSHfXfMYzGRw#{j z|69#JKYkdoq~nKx)HXA#H~I{7Ry6}vXsk0jH8#R4X2N`z7*QFS^L-0hy3y0+M#;gE zRYod(U#!B@LaCc;-(C7hsuhu6gRC_Imgh=S^+& zZD~)(PXL)=B|tFZh4cKD3r|kSDRYOouO=%8BB_Quf>L-Xu#jwh2Fe*yp zj;bnMk=pX#qP1hlY-M7o=zbrPo8Qyd7tq6MOI3Ie!%8Rxt|}ymvKS6o{&{OryLp=< zfWML9(KSX3-X*M7=WCPlsZ_G2&OOR%jDcapPn^A+=Q4sHj?I!<z3mx&*Y>G9lFKrtdU)xmB)taWUH0*b^^#mTVxjfYInq6oqwPyNi&M+_g+f})>sbkO!=w79FF5cqf z(;~{fy4(dvnCwAoij;d+0*2k!c)`!`^|9uT^e@W!d27-E#S{15Tgfibv(LP&E)Upa zD0HOC+P{C(x0VP=k>C$-Q1E@X-oS6?afiA806(t~wZgSt7{h_9-Hd<5ST`Sgjt-Zf zL^nXFgxLGnx3cgSe(}%GWlvU%xlcpacUm0L9A=)Pfglh$Io2cY zrq-WDhCTgPj76PcngcsIHeW>^l`7dX?HM&{P}5iBnux?9%vo4Td5 zk>KDX<#)}N&)k-yr2${s=2U5Xc$z0`R<5L-MRF29%GMBT=Fv}H=$>BQ!?+jk@4{e z23OcWcsJ}iS3K8bS55xG$1k|=u{qjxN7AJxs_MjJiE(7@Ir5(mUQgWVf*W+z+H;{y zCOe>gO;U#ME|%s)F7Y%^MB_#9f{e>{ZP4>70#sSETDJ<4CZIpICo`#>)k+#)o;n8h znR*DEySy5)bdVCHt>-=V8nKjSat`JoKvgpjk`0l)>%1AV{+wExk2f8-V#SX0pj$?te3{|``ph%}fO z#^)3~l`RR-T&;8R@#)BgbPc^il|r^#+ykwL9|IX&8y%+>ApTC~4w2@dst=y%q<*0B zLUb%t--vVF^xutS*WmOccbRDDz2nWz9*VTRWVi-(#WlT_U47PFZ3hm6I#9CDr7z=7 zV~t&d&#b-mWNbxUN39vBg_KJ&tN0+tMjB^fsez2?9kuWNxBj1sjCeGZeibotU0>La z)s&b@4Q2&sX?gTW?NePQ496^HgErvWr^A`tZ?ys9q>yyjm25lMvi_`xVA~BthCYNE}9HR`$)-Oi)kfCYMrR+N!ui+DfN#d27TD- zEpeZrO;UxT!|MlIr5`iqTdzaN>?47C5hK4k%X>S^tv;m#9s$Wi>XG)T>f%d$QN_l5 zK_nRjgjIwEDE#wb^hh$-IeO%;kp^Z;VmVqqTK*C0C(Qd$+na=OG&5{mRz5B@YSwDq zlpX+2lEv9TQAmiL=CMNN2TbkW0Cd&ugI@jftoR5FiGv2J{$qeRcL)B|uwT$zYUrHF zlRT@QrGu&4gVyvO9SN{`<*}rd85#e*)xC1h+uQ$XvZbpw`oynY+SFgtX|gFOvT@z) zQu1|23%?9a^L@JOy%fJ>%j0#0z5Z}dP0Ls1Dr-mksv`sU7eZoOYCw-a& z^CVF?J~|D;>#DKV@46s2D}lvJw%=!Sq5ft)X|+ct!t8kqS!yYG^i}9u7U90W+(Y2f zaSy3W$=p4?Me#-GKOISyz8MEy%wIY6*R~eBL6hKX&4KXtKn)dvya;0n$CLrV5?O(K z;yj(HVkDt6 zChQW^{#&RM&p}P~$97@UZ2thJxK1qVdx7!`p)Ph+Gwb#I~XebYSLSheto?y zTe%To?(I*WFvT-&2NhYE%EFr*=Sddc+us=&pfLO!DCk{1u~J5ows23k;|mg5i8I%z za`rGQF4r3;Nn{hms|{i%FLpFq=zf=Pg7%00IT%- zKNYk>rf@0&K}@VIMcgF+?Uei>0YQXG&%m=|>oxdK5zV zYS?Dx-oG$<_i|pZxaLzSdLr+q-qG+6zd{7sKscD+6EsAbGT4Qhu_(}qFqmS6-(t%D zlrLxaWF%ceD*9qZndC#_D4h3`kzom3sX#3Tsg810%y$(4S7hdNWL8iWg7AwGzL~<% zkY8AsZ*M}wjB1ou*fDi<`1k+`gIEAY{AIgjN=lr94cNd5EzP&uu7#gVEgGgCBRd+T z%mC1eLjilT%9zqOqk;y?r-G1VRy_vLOR)*-ig8rD49+pEcoWgmlk8BfHMrQvmP(zy z-L@R3H64CRvb^rhLe&R}{;hJ|^V>V#)#skEVrzWnAJ^Ur8zW2z5L78Rsz)kSRw zXLUG?tm$&kDLnADW1Ou0>(o!9!Ko6!zSV+0)$tV5NA)=GBMVj`s?9-jH?Ycr zOK+dxbR5B4OK)h(DrW90YelGQu*g`JwM+DUwiCNld>mp0!L9uoW&IzYx#2O}3J3;9 z-`sQM)|uR%yY5%_{0UEyldZ*SEEi@EG{VP^Sr9D@Dl#MK1*CDZQ(uj#0i+d`D2e0o zFl{Xa0YA$!rrZ>&tOeqxhDUv}g&N%<)(_yP@d-$5-M4#L=mlMF3t0BKdm0WI!>>y; zq!Zd(w_3;^uDKiDIq+GZvD)%qCxSs?%XnRo!>=;N9Vf`VId6F0HW*$ra12^(-U!@H zUvKq+fKOdaBrOD6kZbE5E6)VU@DoG}tj=rHfA*{3!Z1R84jdR%;NV6iLV3lo3^=fNqO8{SZuIjk-j101+{wo z_?ND4LMLpg&tf7dFL_G`CEO-SEmB$MZ722(w@jMqUAgczbF*(-L8#o9buR>WGsn2C zRs8jOBxL7V0m@^2C$a_K|6}W_f3*tXZdgFYmg^+-MP#2bkN}6g$v^k`*WH3wYo^bn zO~fuh*^w)v)%PkYan5n+V<=36blRzB5RG$=`5(HLjXPIFKJO#+=7z>NXtGQ)OR|U2 z%q7b_V)9mcXy3D^y{G-ldb=m6Q-k%xS@rmygQmMGQ*k-;2PJBFWd&?SBygxfrv|F^ zJaA~=%KMh>+a1DpS8jSn*q3@yE3ZHAgDP*e*?-+YIPaqep4EtUgb(Pap7+s{AKAVc<}Ig{|0*N z{+P5uV*Ps{2v9FIRVlD2-*Hb|>})!aqMIlyQ2k;t?~OE0exeAK%p0k4U51EvF%N09 zOz8F@a}_)?it{h+a{6k2aJ`NQ`73ZNX9W-%LkR+UwK4!zoi>^8X9c@I(TDMW&^yc} z+R5w6d^{c~)WC-QsxO9!~^Nie;e%hIqZ$y(}m0&r^&O+b|f&E+Ds1 zkK$@~mb2hvx2cH!me2RY@1x06l&;tg#u33x8gr`KBYFu5q2i!HsdYJq@hOCX-QOzCH!avnfh`b>R0FkCM0O7`0C69d|Tr`@ze}Ik{I%$ z#~Kr&9G@X5k?;eJv2bs2;+&?2+w{9kWDJi*-i?FRs{5y@fLBdt7nG4sV5iKFnEkKN zrg1vM!oTO`z0Kv&wO1L)C)y3s?|oNg@m}wbL8>@lPKB!kT`2bpnsr6VcR>yTs&_+6 z7^;F~J1+DRXqD4~YWenTvjHkX71r~M>La5i51}PuPPam(iTa#ele{`4I%!E0i**CD zi-n|YH74gJ93~w43L|Kn#CSa6;dnt{3(`gj_T54WS#0tX3XK zcGV6ew8YLN0&5p;tvMp<9igotXVb;bM~Ls(LaR`@!@Ma~MjwtE5e< zr15l~_b@AWV8)ukH^b4uV=0=!WaCJFh3-Wqa7WBGBZ3#l^rHTAk%CF8mIE`K?PamE z)n8PID{hw2J#l>q_Y1@Du8f!%MNF<(e?0R~MjY(4dGl7Yabori6(iLQPS~^*tpmF^ zQNQt-ok|?E6m3gvD2=jiW>%VP!f@uS=5*N6;y)ojfpGx+O~fL9<&12Jo2f%paEtl) z*x&H|{g8$FSxy}Fi@thH+@)Q4NG33pN)}JHP=rg@|W8-N^jZ;3E@XiEN8>unGH)q z2MjmatTYwiBGrh>DHE!zJ_eR1IsVEZbpIpY15#bV{oTi$lQ-kUg4>WFK@A4r=17i3 z*x_?SKk+rRd7q2dn9-UC?|2q(C{)LkaINU^T~ZiKS5ewJ zOTY58<+A!>(~!KO{l+Ur=IBHF|w|2UiNOWK(vflDWa4cylm%j6r-VeRzPB% zbH)06eYm9++_Sz*XSxlxx)n9kJ4#BMoOBIHhDi5mu9AU~okZg+t)GSR2t7@~P=uLV z+L%%M+5pOPYYJLNG|Q3(=sB%|=H8#65bH>7Q9GI7 z`&V=7_gz+X4_WTh9~>TZ=C+6o_X2K9(yX16SiCA|a)Vn&<=SwFb?{ewW_R!;9NW7O6fA5$E> zI?iRlJ2l{Zb^WW_rLHx2?JcMo{-tBs9BiRGb02^76hFR#%#T!WJ+>zsr`?P*VbU%4bF2FwYA^s3`6?jZ>oeH; z^XGr(Mx7723`aeSuIt@X{{edHYan*;n%3vi#sH#Qk(DIz({2eZ6wFcuM9dhi@^kRE zN*^4FHgn&T(zxwUbGzyq+zLj42tx1UZcrI55FZb4$G)hHSmVO;V%l_y#Em zo?VL99}ajs{O`21Ee0Nh_4>RQc*P!%C(Q1y#v1WnBMTU=Ul_=@D8a(XAa?wdBqN93 zEK46u*enQgDRnI3C9N;O`42!HHNY-{5wH4QWm;Q5VPyoGEpT;-NF9P4A+R^?II{-)dD#Q53IWZ)nT%Z4YAfe zmF;k!KD)NTP#=w%6&IlG$Ceuu`?5PCVLE%XHSN~5 z|C;z~is|)47`a7=+i~JYN;Y-k;$qAZ>o-YgU(DS0R4AhDQw}iYH-H60D%gi9`U+Y- zb~vd_LWwVaRc2ki0amBl{_1PPE-ernvW*%Z_{Ee*iXLfzYx37lj!^4sO6Tiwg|h0Oclclm3x?1(di;mI>I_UrcZtL_+g<&U(k8ye>F`M4WFRb z=VJ6{l%y}wW`l?CEw(33c+4U>n_gWb?I3JDc=qQgca zE0=oFll$ef-amXe&&Se68TNOm44Qd=lYJ~nUkbIu98#4EZ5sdAH;@3Yie2p51BMY zX|--0pz4~}EGE#;%AWObc(VrW?I2!pwQlIZTA8J=uV1Spb9OV8x(I5C^Nwc*qn5fD zGtj%DPo4rl03&7hp=cE>I4PVN7=6VG-wO5kGpj%Vm4%kr@|7lBm%Z6u)PG@7zQ+6~!+r7}0kBH3}W7VYVHGGkl^A`g@a;K!_#% zt40)cm(nOosUQgu8meN0`=^|eG-QC3Hg7hBAM+&)wLFo=%D>0p*9&taB$byhKaiQl z7b9~jr(_#$3ap;TCA53J0w$7IqAN%o#e`R2It!yYhdGfm#zJ+QX}rJ5>FBWY--Iyn zsr{8BdPr}YDK-GE(9gN?VN=StgjV-`yGi|k{tEXcntVAI`isb-4>Lx3!f?#@pKu31 zMEt~1{8mAN@n&W|gR&jHuv4RnOn$hn+~ZC6vRqtqB0}<&&T-Siu-&jo63^dahmLF& z9+h&s1_vnCMi!+&AKB0M$?C-HD!;yK+5DXRhe5ha^;sx~mb*X9N5pM4Qg7J)X0>6j zEEN?Ndv1t% z{*HBrF9)5!WNd6S&$F>;kW@_Y6dVck@u&gshVA*>9clpQM0r? zo{ylaZ7j&tr9T8_o*idiQuUq&Y6aEvZDuL^*dO894!L*jSp1)@!I(sL(jO(SB^hb# zQ~g-%G>ZlBbhy+y2Cf2$zgH2D5f^%R39Kpetc}IPHa5r5);+yEu)%}CZYU_yE&g{s z$^fs9>8x_k@N?OJ6|vBBtqDuj{+)?rkbDeZ?ij9H%;jdVdUHuHrJHmAXXC5B+NUDE z2*#X)*FO~}jE^@MejP{&&S0>xFRI$;nvA@%6?<05weJ;}Ptn*yU(>f@B2o42JV2*m1W9dxsC(E-b?Mhb0-p~ewDgKsSNR=di^;Lxt7o~v74 zy^~F`KgZWkIl*8AZKwD#Lq^F%`-n$y=tI!$Rs6U1v47r^#tq1#lJ_nTK7Zgsr~xxh z)EeovB-?)Sv}zShvG>#pgx&f_yR<+d-pKMKeGP*8JBGt(N2Fw3VstBa(6P6nUhuWl zbOj1yyWY}lTHg#_sN&R9mS}!wWnCLBYzNgd5aLs2#$=jkd9i%44wG^J{P{mXFB}m( z*-_>NP3`cKcFn$zjgNuzpk8^~JP5S6z7anunYZuLw%!x$g$g79?_C46q=THz!)Znr zY}$Tnnp;rp7%o*Y9kM!-o`Ko^8a|LqJFmmar1rT!$(3I1(<(Y8S@e{>tmCje_VlB{RvD@RQ-OD%gFmx2lOm()M9=FR|Fr80# ze$LW2GdyY}HT);5zV6>6U@Fef_A!o$7J!uM$E%m)6M6~Qaap+1{FFoAlMh2IUgmt_ z&#GfoWgg_e+0OeucGs|=CVU>IsT5Omvp@3j9Jq4bk08!td1~{OlQa_c!@!8I_(I-% z$&#;|jC-HZr6zu0pyfVI7&W9gJWw(Jt-xl{{--eq89D86o!pMaqgY7GiV8U%1l76#f)xt;8bb-X z!@cOS&!|~v4DR1?1%|U?DawT;v5a!KM`XHYZo+AWaxB!ozIZHxxbt>s8lx4+7yx-d zluh=5MK8|p?L;?frBF24ceO9mNI5w6!yUvv65Zo_o#VX451^M%X^L|-Yb6jUXw)Qq8Ggmy!EcqHGn zjFLHXJWaFY&Ho;T6j*4oEgcd%8F~tJ#!M?SW>9O#e{f#056!mClE<@ax04$CH@N~`Hyl7`0WrfKF}<{FABm`{+$*maE!Ehnb5bhX0`)$ny6 zc$nF7dU|@YIMCy9N;p!Y(Wt1qk$@cPm~D)a$~!Swk&~8XQ%S8tER=+%@GW3CM9-A4 z7fn!=qNU)KGm7q!gr>4uPAtwXXS4z`W2aHUg*r7m6~{8)g{C?SB_Ne>UNSfpN4|N< zCXAjy6?&5u`mIRjZcmq3g$^M3HkW=*ZHA%vj_@6&0|5Q!Z=6gPJvm&wcOfq{abHrh zl&Ewn$i2E?FE9PpflOP}1U33c%J}Nzm4o z9tZLEHs!{p@zII%TV&N~H@AVt_S)L&YO}c)m!X(!=2QX&qQ#$a>;&Wr0?B$>9k->O z2HgH1OJ^Mv<@>$=rMp48yOEOa?(Px2Py~ z3JWi}oUo~%iqbOvfnTlnJqG}xMUU^*=IkimI63GSFKcmA@jd>ch3L((#)yuPBQ)Yk z|Ef_%213wP&JlhJupkv&B5LE1^N6DASN(C2cpl8xB9+V2ULujuH8u;Yib&I9OBmH^ zC0{}z^jKhG_DdgS*Iw$RE2`7ojUldoRuxIqwOH!>hT*q|)^Z`u`RoDp#q}xV>Y)Wc z<2fJG-9ufSyKMuDaW0(;E1zCEU98seUYeq!%c8Si#6!_=t)y?Xx33E;%|x5p$w%ka z- z5Xzm8ZmLwz*+V8`yTQ`*;x=j{7cKZ*Imf1VF#9;=^|ONYj` zs;r*82QFcg_5FH4^e5TUdJ59H7cEnhV#DeM&c;3u})($ zZ;J$fc~76H%e?JmU&_A}0nHhWvra@?J+9hTH_xlre;h5dt`W?pQX=4ATK?KbwzMR1 z&Ne3+#ccgXvWz%UQ>ix|O7fIYu{?hAlYf-DAvksNq*3CLSQ^*@@d&h6mn9eU6t_!J z#L5aXNkUGo)->u>WAP(pkFu?%sGVH1PpTldpLm-Nh$V!B?+wbV3DU=MEC%?O@AJS~ z2#IgWB^20)5gEyAzIo8|v2}T1+T~o82J6(=i3VWL2~PeXtNKGX6Sko0f22N&DKR0T z=(LSXw1NQGl9H%ph6XHdgc1!8}W$CKmmBSBCblP;xksV1`;us|J9oYRYFfQ>u?pvk#o! znfU=s30?B86yYjH(^_)f`?$qY)WzpSM!_M+ocfrCc?$*{-g0~Qdbra^)apb=VYri4 z>nw@~6=yEn`DBJoSm#I*Gr|oLGjO3?>^``blv?bFxe<8^j;r;Ok8Tri>ZbfSutjW;NWHI|8_S4ae0(z7;;hP0oybof(y zfQ=c3aI?3%DhpOJj;l^)PK>G`PE9D7#A&(#)V}CMsI&l5vC&gXL*ioxBsB#32vuwn z{@Kp}PKzBOhwT3fL-?04bT%?JByK{b5*zHnA>h=eU=t#WRSI*!BnRX%K0aqmbo(4K zGB!*KrbL+`9T`F3fdCQ`GBGiR3^4&VLX=V-Izx5Z7kRl7mP8o^kV;qywu)IbsFXiS zMol&X$sn~Lp*ByZ3S^h!!NJc_8Z(L_m&QaVr$A7yL7;+Q%Ll?C&O;4LCr6f5|6uU( zCx{d|@6!w@2thbbqg~M$CLagUH>UFAH>z^v?+-f@SswhWA?ngynK^;e!01%9q|n^) zIU|#9#C=A$BwcDdjnkQKrz_#t%*VJ9e=6hAf?8b8ixSRva?q|myqE{>{ZNmPw1eR6 z$&QZIwZ$=79GG~~?z}$i_;cpYer_YzR11;Lo&0uy=T;OH<1Ir&7yN#T=l5H%)oyGk z_g`fN+VLyFc1Pq5Mk%k8Cd`rke^5%7?z<5qV06p+a5$?fB-UjrDcK{_UNe z>rY_c&(9JCYqxz=LHb4+kYgg%y`$6uPJI5B$Mh-7^b4wEo&C|YU=?lwRso9^j{Pk9 zxC#8o<7h;;grQ&ueKiDsDPBY~j(4frLMYiXbt1g{ygV{``x@mxzR<S>B+o?p-GFWQ45d$dEKjkm6=rQAI` zFD9KmjDLp)if`A?qPJKgc|l~RU30hsJS*x2dbq5Z(2y1MRSzzuI z%Yu+ENqmIN0;4x>Fl&s9;!Dcg=j2I~las7Kb5gyNbv!RDKZBk7bw~&F$63Z1m(ffmAPJ?jw ztu3;gkInNR$HvW<7SW&Q-rCHYkl?Daxy3Y=GO=?VIXSDcZ-OV2(vXYg)tIVlDwRq( z$lJP5c~k1VNA)zhH3LJMnH%OzzaDN{MP;_map?*^{7_pV|9YIy>l`a|avo~y^UI#6 zzID3~duWP8fEdoKUPqwD%twFNn74p2k{$u`Jui<5RToZZ21n? z+jQ`6gW-u^%blK>3%?mvJJUsZ<<)tZloC0(G^%G8F47Xs&a<2X$n_(wiB#?A%?wIr zK3}4gEanmz+EMkY6=d0~T7A*IHm8)DoP>96p`MmgX%j^Od+Tsmj?0yhOE*q6ynV6U zy)6?goLc8nQ{kk;Knz*n93SiBdMtoe!oVujT0E#LbK@$YIl%b!`=Mt zN9F5>*Eu&EPhjf^tDe-CLIU%t!M})Rj(Lf^T;uucHu=b&rb``?&$9dKA-`Pi=HNkBwGg4U0IZa6q$qDQCz{tY4751_|UIcrvw%^$C++3oj z+5u9Nf+ueAl0rEzLT5VMdV2p6IZH#nAA4^9^R~c?9_m`*9DVGyNDp?HCuQjue=oKr zyDL%XG&&1!wGtvtD)w@9@XS`LSgA-RzgS8i=6e?S5+ayV!L;>05eutrrc}>~+`u-Y zT)hO1(jydD`MdO-bm+Bb{gt8M2db?X4Y7-FFe0c&gy4!87=Fx9fh2765 z_w>TN0{))dx&#)EzijMuv=^H1ye{WX{P8h=x$|;#``!nE8J$*Stv2`Xr>PV5UG`+1 z_#E$ulUo)h!}_|HEC)IVAfD6E&|gUw&sXu*eN7)vhDQ8-CPvS!d}p5v`%ZWBsoRwz&E+U+YX$A&~XV-6bnTx^5(qV$(9B|rL;QiM+!%T#W1^j%AjU6DYB?fSife< z#&P5ad)2Z~=y5K>#YjsHKcE0PVqbws!wmdxkf0%}7K#Fa(_mSRG<{PT zvBF1_)QYzy2I)%+-v&wd)WZqd1dg1fe%Komsp}fnI$Giwi;>UAR(!>GUXRMu+jMBr zCQ_|+FGmLR$^zn(z<8l1dl9qIi=zABw6oKf(FFs#x-VZ6sC1JNt*`_Qf`uR|%uE^i z?TZCTE4wDWNrtZBF5f*lHR*2i+hjR4SHsej(ufny;*g_Por2#c|&#g}kKmCVXiWT0YDsFVOdXhRqNB|HqBI0_R~ zl6Jt3E{lRFV_zcpOGf8w8a|Q=osJ?3q9FR@;E;?Vh=Cx7i~xxtRn`UtGzZc}L01jW z`4Wzfh^moeWSZrY+eIMfkS_AKd{Xd_Sh|gl@ED(s;>U=v3^|n?g(<=sP)fKCCPD_i z8WBGozn+7f%+io*vPWRpY8TPR9bcqg9Px-XkDl3{;jZApGm*Z{Q--anGhY9ZQ~ooo zXz|{e-mSnW%L6h`>)urE(Vu+_;}9%D&cb!vt4LUBF`RAh&OmY>?~>tFM)YMbc%yXW zj22F}e;3i+O(pff>;>1II~8nn#7^!9?G-Vzw?waB(R%Wr3E>2`4gwflhn0U>3XOBw zOttJ&ORt{`G$Kw}MR-gvd^1Fo!IiIy6$t?lcMo;q7|Wo@r+U%lzVP*||5r>D8SvMtEa^OnPyby#&VW z#V=Bb4wa-ihmG00SXOPixQ}JL&ET7u0EY>oEs#~@5w5_2fMPXHyLh9~YW`+t;aqY2 z*}M0^9#@;)JAI;-bLsKBcdXA_LNfv|RqFeDhXoh6b5A(Ddrjq{Jp`ru%vBkS6jlgp ziv0)7Tz{W}#cO?Sw62m>S4FKfn`UMoq(+=iSC4Q18Ex3!v1Hf;U6oCwOO0RWIo(KU z_pAnrKjpizYAMs&S=fC%9%=XDj5e8I@JO-0<(2<==t$^mIHS-w78sOU>lnRf&`z1@viYQ}?>L6m+%D&TQGQkO2v#C13HN{4E2CsD(RHw5w%Wy2}iIuD#V zt)x>7hi<4@Nt7-|Z8?@1->|bcya5gFFRB$5E&kXzd)M&i?|$P%J1J^fiJ@dTzE=s5G7qEK_G84;^q1znG zvrE#{n8Yvf85a&hN9^9E)ir?@JJ;hzvL_XaTvtNQjS<9~7a!WXs&DTfn$>~(EBlZ# zlBlaJacU{|+jQOJjC9T@z2~@8B#b^LTjOm~L@`awlniCUAwy3d6?^wQNHu-TRvA+( z3(!fFXRz&(QGkCj6bNwGD20e3^9;n+s?Fvt|14vVqA($M4n<1cx+nkmqs0zKnQDxE zlW!d-GGxKXeO|1@Ns5)x^x_(|?J+g$lSi8wJNi}-_A^0SS0q5vm2l^Q9 zJp9Nj%-LQUpb^x8La>V(sBf|a88 zab{=|n%XM}?k)*@g3UViN<5Xd--qy>c)$WGQxy|6I(fQLonN-X>H#a;k&|-r+H_t` zx_YjsaHoV2GiM1ep03GpDgJb#$?$mzDGund&P9~FJ;J5(q&2CO(FvzLUj@O;31~>d z_!|T>F>#o5ne}y47W6==)%56*$k-_Ifba;4_(&+2f~m})q$f&v1h}=3tDQkU8A{*J!Qmm=b<|DAP%1qZ;_0!M1b>R-#Y{zxA#{w)5 zF^X4no#EX-#@>{!Vojk;v_Dte^?A2HY%pbZUq!$0MF%786RTsvxRCA$L+R zs2%rWt&B7aw!!a^upte1x(WAKkQfZ8+RBxP^^?(0$850>z?Q0>;NOW+*guo_QZU0) z6nQY%>0nXAY)x2cFz1O_FoBVUUc86qwet=@)AcmZQQr0&)9SmTjn`~368zU@aoV| zWOK!(ixGZ3A4Izl?QxlLS+kz(gZgUrWc8kO1-e>cZwi6qtJu?K;(32NYDfv}KB4^y z%J%rX3~d7(TKn4GjqR{yfyzutseOPT;Ca&b6kI&T%5!Y((lhUNXR-k}{??UeUjOEQ z_XbBu4bZHc0pMopoADK9{z@ZhmF#G^&enyuL)%WafJS-aJiV@nHp*z81?#kssE@eI z>5j*ze=dD9r~iR8yFI3O=se~;VkHN@yfy=ye7hauRvT_gXI-}a1B|fOffs6NH?K<` zy@q!C1&_VMZ;q=jpH4oVH1yROW^obT1hfpD1U?=X0{MPDZUZqVVqp6{ed;~RZNo`O^+`Z{8L+X8} z37^0DK(cu<+Q9vjYg=n_%ELh7tMrC*qW15O_TUx4ppDsEXCSA;Q)r?uOYM-y!R~72 zB#uhcjy1IBuaB!xgqX28T9es^FUiahdDDTX&~-xV^4m=}u0PWTtT_{hliymX2)D6W zH&rD@-(O9uW~TCxY52uisB!A6%I(K;yDC(?*CdxeC!OGJWF#_PN>rH~&q?s$P_aO_ z$7?K8-AVnRIKpXxc0B0!qD% zEy%L*J*l-lFG)}$)J-0D`g}96Z(>|w*jwV|bmhrIIoRLc6KYP4_BKifFHN+ux$3Ky zGG%*e>4LF1l}E0G|6xjJBGNmO*(HZDk_3&`hqonmx12(9i}`5L+q}_BB=1SkpfVv` zPDN~`PGPb{N;o&$d=YUR7=HH|;9PB;oIQMFD$IVyK}ir(CxOVp55#9I?5$K`kaiM! zHo+to71B}J-ita&0FQ8UJTgp?1YxuV5=M$R+LFaq#!wO;_9hRFn6E37Ru8=<+(uOs zF^>z08jT@Qrg-TMULKoWBpnZ`oi(8asjLAiX4EyA31U(==H+OFSp&*@K1|93PMJhr z405KZoK4j9Uke78{AE-)Y$n&}{&|JY_R<1|X^D;AB(Zr{rkDXK9ID;J%saDSVh*V( zGZ%W51tm+x+NcGl-Z@1zrbv2~C4^Kt9mLcUIYfm>j&QcrFDeT%suU_|!VO3qi%vQ& zz&yq%ANVo_Q7%g!38eCiQO7_IFhHYYBP+CNxts7Q2#ddNS;e^1&k z5e#`o8LcH+C9$l2aP{_L(er?Mw8Cr5((V|s)6rKuA)VWIXuY_~o@m~<@7%@1;J#>` z+ui3tf3pnqN78B7u%28EQP>{MwlWf|X2RzPl{&sdLvo}p)IB{t_6|!&JlnOv*p0Eu z*K2fZpu3Uo;1o8n?P0lgA?D`4wq^ShSe3D^XM1}7 z=@6CqLcKR|k|#m~3s3iUMqD9Q@AQ97@DeYYdmay+y3DjfCddQvI{p1}1ADRw+Y1so z=`LY)s}tRK#RNtj@uJ)vI5?aia;coh9`f7AX5`bLex5@ADq}u))Ja@eB&mP;EL@-D z=dH}#zF;he{nvp8ZRuHQ)iP;=JcPSg)V|gcE&L{w@{>=X^lg(s6WZ}WdNJ?eE6;H2 zYO9`L>XAFX|A460GVf!?wQ}`5>;AwsmkHptZcYM@I`~R)tpw@M&rI1#wwH{<*A@FI z%fdSM@ObG4P!ah#`vh*UdPOLHMaZDR2)}oBgSv6Srr73S_6A|rq`OCo&X00LXHmRD zJ|3wOtWvviwIUr08L=y>mw@#8Y+?fA!5WD-$X8d_6cuXWT>TRR=jQ#V>n9zF*=q24 zM7@J~^{sb(g^-K+hgC?V?qb}I|NXUHnxpyihJws@qCZ!R$pK@**(T>*f{z7Ju7h~V zfbF}>yD?F?Se|?*ckRKlY3J34JEDZwD0vH!{P5`l zsZK&aaIDRiihTvh!=N?}^L<1S;dH?!niNB`m4jy$kKNv&lzcltML&OmxDf?sHSV~Y z0p8|Xz!kFTON;qkw=f9r*OU55htGy*zUxZ|Yef6*NzYU<4$Mb9U-I?N(Z0#^rEjJ^ ztxY`twmlQC^zf|EbwK-_RG+e@TPxlh zI_7sRHNPEN9ppAq;l0u@HyND_xXQQg<%H%=Jv@B-%|oG0#*%*R(AFU9kx<CfC$w#2unWjVdF z_1hns*@T>P5{B50qtL*uC+oY1%uD7`oWULTAv+e@)@}Fs2Es}E@BcEpG}7JpJo%Z> zXla4-Nv$OA{GTKv+sF9Cg?(n^dt9eYDb0iMB;jofPKddqmR=jXPzTQ?p;?N4vU3%+riKN9_0cao zdIIactB6iJo`gIeXM@syww97%xP#i9siz6K z;Tt$2>)US_K{LpPkJ~7FrZyJCGHv)&q9ASs5|JdS62%cF-H1Vcgh?94TegInFy!4- z=BlNn`tRP=ge#FKR?J!5C3uK2KQWbPR7$uY1oJ5=_+6#3D7tZxsR2ae>fAOhzj)q+ zL~6_sN8^?wJFED`>5@lUF=SzV-*=vllS{|GfmHuhEruI-Q?1lfg9kVFWX!TQh6e^A^wVc8K zW=EiM1{r)Emb43yQW`K=LO?=H1x87Fz{rX4e-8kmQ8DMN!(-Db3G|XT63n^_AS7uPu603($_;|9WKJzD0X)$k42r?MR%rb23sY8 zmUvnFk3DAsv~6!eck3RIPti4%v0GVP$P2rEQ^?Ics%gUjHteSGxX;%EsEu9fZ@g)C zOj?qTcVZz{)OGU%U1N;_U3cPHj|FZ42cz5Fdk*!7lA#^>p_c;f%~L)*c`dkCd~aD* zN9z6W?)-%xMgq-KgS%>Ku6u+{{%LZ(FWWsaga=EHZ#);a!~V3EUQ_-ra=oaE2kO$g zy1zSr!s|5IK`+gMywueE*vmdFhg(e;gBpbZOI<}Ut ziq;g(oS75+3eO*`G-L!l1D!@Qb0_q+cxE}3SLf;iv{+ucu~{svAot1N;YKf>w9ccQ z>J~oG$4e5o)X4_2q>5qth0`UMenXKEQh-m1dV8#Mx^`OyV9@^qJtSGDc(guH6%%!w z{1Yysx$~Z{drqe9@Mx4eJ_=1rJ8@g`G5ppr!x%E+{O57$T&gaSTNCOCcUvtt>?+}} zZ<20qbLP}{S$KoS&~~VYU1vl_75n)>Rhvr^r-EQ80Ava-QeFuPZe9f(!+-klvf9|2 zR?t>%tzQ${{%^U%&HBcAYCga9d2LwLKwJH1G9yqff!c5y65Tk@xSw%y!uwcUnq&=B z&JEh5WLyf}i2WMHw}XbBV|rL|kFyt&^c9vtwOVfR z3!){fWVW6y_?cqyW0v8D`7t1_tdhs3dANi&v|IFvdt>`l_)f6P&%s@3a(f22uWJe;Hh3ID*2UlIyah?{7DMB2??i;Q43GSY?u%uw+5>yE zeV=$VZ%VJ0pJ=rF6VJA8)=+ehd?AynP$~z}^ZTfWx+t0y@cTEcPwDcCJflYOyvyMCsDLYFxgr zlO@1cP(W6YK~^9pz&9YRX)C3Za@>D%^I5*$4hdX$dGh7aMm`Fuy6llUsY{;gw|4qF z5Rfo;zJ7IhxB`IVlS8EK$n~n2@B46BV-FKq)-IN^1~h0(jnPt#NVY^NG~E*t&O=iw zkC9Clo+EXp!6OI-=!cY4&k0qFF*CqK72RK^O0N*T4z1b{Ziz-9vz5sh1F!6r{Vfzm zv`ux+{!;>b)5ztWF{UR-uq{-FysfWC^5b5VJfo)dmcyUr9!5Y9s#lNHI=j;R-{F2HAf>eNh7`cHw^y^w#8AW zBpcnDiakvJpIP}CDFHr7E&uX0l2H{?0dPB%%9h7Pa$p1QHw=0ey-7WET}(DWP>fK)M314v zrxU~uD`B9c55te4Qz=3CfRCT5-~=kiPi5i*F(ZM1_pkxB3_`>k`&5I}tZ9`7;;-p8 zEUt=-bnezpu}+2Y4H{eh3nptd7S382J}pM4s{HMulmhgpDWA&p2p!jO570Y^%W68e zrvCaCP|gt8PLJhJFv28WW?%^orz~kI?cyEQtok9Buij*ZQajy0*5+6|ERKB;U$>W< zB|>)KnVn({xJh?Y>*t-0WH-7Sf_SCoY-H`h6mMI(TGWzOU669QR8 zYC?v%T!cnUow*Ir(iRQ)H7-LlL|_mB%@CWDXFQ*7u(_8izZ}<@eMsw&w?jfp$6Q>y=iGNb8Ms~cbW9|X#EXI%`8vI$c_d$VG&?!= z^t(L1zkYT5W|aJWxq6Al7h<>S?i)xu(;QrU+E&>5<@h7>m}dEpnJd2g{Cc0Y#i@xX zx>VSMWHb;;>8gFlqj9tvuy4w_j+jIP%UL#gn+1GOG-&$jW+JZPhXKJFvfR{^lp8(Yzt6{O;;2YayRxa6aKG z)P&i-#_=^H2=ElVyuFhi`$&N)vMYX)tlGB za*5;0LTxaVkK1GC9eN)|XdtA45b!(Cx&|>VXZ~EO1%LrxTQ)s$UrTANm8oen9=%G= zI{^%2Tk*&LK(()0L9nX5f3cX7ydo0CMb`K217|?}@t2kucnXw0Gdyrizjk>Lsya$W zC6c9)*B%sb`YfTXJA_2rguk`5iF|{$HRuT}wqGLVdTh;B9cJ@NF;3v=u*TXvPJ}x$ z6(PToP~G~)kWvyXBW++{Db+vvj3WIDD>;u#nlMj`d@!IkT%TlXPtyn!Ty?<7nhZyI zZ4e=xYLrozk;>k>qkbuje`$bj2B0OMQ>J(HIApVQ7Y$4H8jFD+Bzn^+4HT6AdP^z0 zociRq*A9GY%#<>{fX{!2b-?Ubj`A*)CoP{`oiy>Jo5hYT@7>hNC&nxG$@s4V(ys@v z0A?mh471*&rE2eGK)3`r*ZJbZwc-d}P1{2w{T_VrdF@cEcH#h zAH0{~-xkOvF{mwQk zt;VKcf!J3w9>^v5G&ExY@w`z z7(${MqqQ2;Nkj$)O!P2zuE99QcV>A)bcX(Yr?Ztoj&rxCP}r2}9c(V$>2g36I+ran zc=HI|p1=+E;=ey$dcU+CggbuH{Mkc zn-Yyj6riZ-`lp-l*CDbsaFEBKA{RPQB#VV%4rX|A+-KRS`hAF&?!+Te8m?aQGCUyM z!tuW-C1K`dv4}AS)xPDhHF4+iVwn7qaE}X){u2>nL0kKV%A1Ed?s2kI@sqPf^f4j} zX1M0S=yax9-qY7g<|W8N5o(YougsKLKwK;wD^X4CTV|i}QrsvJN0hntH}dBp!^Bcd z616#m_m;|&W!ENv$GHKA54a5vxXFd_rwOJEm4Au+_@v3ihpI=M2rP1<0&+@)Q^iOA zSpii6Rbpfe3cxUpnOe>Cl|e-p9g|)G-+_saP+_u!fdM@Rlb#_g14B?zql86~0RJlz zGMy?mp%Ady0#XIeeHjHsMLI&{p@iJ`rVf^mN2>fz5_tq@CVP#%8TDzmuFi zIP+DN(2{;=uTab0G4yLGT5t0npShoaB(^ee$*AcIPHUMQ#^>qwD>(yDIM3ai}Y7?6qR^iY(#?Y@artz301f*tT;0WBbQ% zB`c+G6|@%dQT#J1S;Yo#swKvWW~#r0CzX+q@z3W@wS-=rLPMV{3f!I4crf~TLWKNT@D_cc~V-tq(ae1%OhS*@cRw8(E0A={d%YoVPoXU>;@!8 z$+Xb@99G7$j9O~LZqNd^5b!xjGwDMU5pt!L#4xMy!0`ErQg@tVdDs}uMb=;7g=TUo z^JpE+hpqj%A=zB58u->}*jfejpz=K-l`4#dmH05TWKL zj3n4dlET%x^gYy7#$r?w>+mz;Wm-M>M$YZcf!P6C)A>84+uZ>G)PNdT{V@O4V_ji+ zb-oOMnqP;K7U$m(GKh7xcw9&hNkr zI6APnvd{8Jm77o9^TlE?G)tIYEAm&7m6)kyzClr`VirTuml(j0-npagr#EB00pa^f zQF@#_CoNAy0<-$sM|&4)7Au58RnCHhieFx`w@x%KZEpuM)dl#TP@=+&+Lz#5mmrjI z5&{CC-;hxt0i_`1uQ6&PMf+?82-VNI`;zM1YoF*oFV@^zp5C>O2hrYY?ZM_Yoc(aA zU_Z_rn1c3>CwS11cEYDRciXl-9zvAc1^ags?Q)qVRgJtkMiK>ymu;qVx4}xer0h!a zf=3jMijwkrZPHd>`%7kD%WFtjNV$Ioq&XN-kPub8!HzO>Gh-BSXNOSbXz^RHrfDA% z?cYayeyk2GWQOqA4_oHliSjo;PO^x?RTG#SYA+?-qMNOI(2|-Ziz7l`hLuF6HJ@`j zTK)x6x1rw%pM3q?L-{dTZYiip9fD`@!bH}>Di{;<JQmzPneVr2bqwfq!lA!OS-wUxQi2WnALVahr^9XzPilz;M7+NOhw0suoVtmpX;i^Dlof!r;rd&oC zry`?{6J32d$C49$SORb>R>cM}Ib$&D0FLI85|ADn$sS@#N(ntZdWlRq@)uP;dt5zryiAB@VtL?}Mh7`I?=MZuO6JHfSwIJvkFB-_qSuy-xpNv4l6XW>pQ_##xNoUJqU7`yS(39{Go~G5O z84?S!y!kY(2zYkRdBJ(k7R-MSo@pLePX`izyDruaK7!q)IA%^)*Nj)Uk1fS$x?hL7 z+js6y?zq;5CbHw8Id_j%XSB4`i)lOKdm-8(y770w9rCQpe6Coi@ABmoSjv>P{jGBd z>j^$}?dn|0d%WA*>es%^)Barny*ha0Io=Bt=W?DINc-2<=~pZic<1`A36^NlhrueS z>>{#PSSNDB$W)Mc4j)Z zz2i`Q_rr-W?I~u1XT&Fc)zL;OoC@}LOFRZ}%Aa;q4}toBz!8#G3umF==8zsEV9bRn zM$7u)Uq5@{K>Wai)VuZ`AzMJsI&V)de3kpW(tmaz-0jx1MO`n|%G-n(S6|*TLA2jL zJfl79`FqGtgTTGG8X=nFYoFY=LP?Op)~2+MJ%{JJpE2Q+k9c4D1HlbHp?$hnN6A{O zE$5{2u$J^EYCqR^37*;~O&<94lF9rc;xm#j?YrHpkkp^HH|*c=l6z>oGNYmT={{pF zccu|q5>!;4^C*Q@lPxnUYxaQL?P#A`e*-=NBA{dLiWDwz$3591fzv z4hXT5K^V?m$xuEHtxUEd9l>eUWdjZqXR~xuoX=yEvX44xCZE~Ac@3*@OjZqsY+29Z z0)SclzLw0H$w$NRD{GujcPs9i^YVvAXibn8(kuUm0poKf1#eGMo1lpqwliNbX{KQD z!H1?CpcOmbQ4hwonm8!VZ^Aoz#f|>+gNAPNQoN!nnG0lG6MR6TBr#0!p*MX0q5nm zlotN^;q>0;oqtc7yFG0#GlE{l(&v(oYk%5TE+wJ9M9N9l@#ia#H?t2PQDyjJ!s23p z_|dSK@Yb-oQ-WpK08zHjRVnO@lk3y?@2;g`H6dSA-^}boxWBE{Qk$Bb<11A0hJ>{|Ztj z;%*ISyZITzCT)=1*_-LW;r+Z^5kZMXo1pfHS#}Rq!n_s=_kt20OW$8c?gJtOK_2oR z^5@^IJGcDn6h}mwsD%F(KQ!u>W(#mKXo+D9z~OB*5zF`Bovw~}iO&`501R*a5}7#-|-Boy`5j=u%UZzi-5?%zz~B>hCqNKL#G4( zEMEWf%CUIe5~on`G`lTGdH(OoDNjHV$#4CGfo8LTH1DMcXrBaR;gghbRLp_A_?H_tjPM{Wb{$Qand%D>Lc+R z!SbPM0qieUW{I?faRCY5Y1IWv-8wiN@3$3djoc#*w|)^4w!GI@c>6&9f66pTGL5TJ`!Z@sZtCPnn^QeVcty4oCRZnKa{px_3TY4R=zrV!Mqhyfl!6E# zWQ0IKZ;~eX1u;A>0mQ_PjSZ41X9PK*qN0OTzGTov$uWG80Wl%UIFTU8q#$4uBO~+a z=-_|nKqLSx*gAA7HmEW&Ofl>jEVJz-!uIhFrU=YH?J+7RQ&GWkFeexALgLQ7BLX7R z;&$)ox>U!TgibTCMLGzE&j1~(*1N;yjS#2Lna;-L)UWP4Fv-BLw5Iru;C&)kMC2KZ z2dryT7izu^x?A~lC<*XZg2x#}Bs~B5QXW?6&%3Nahj1X`IprF=HI3B4P^}-j9#ELF z@ry!(ve@9*EhM$aIdYW%;?o!-nOD$2+KNkRk1O6FZnf&{0kfetZ>U7IY?=KhF8?=$ z+PvP;ZAZL#3l?bH2_2Y%&x+%U*Vk{Zpx&$=J1q8k?e0LbH9Kz$g>!=O+HA0H(wi<` zmV2NfGXpomEB<9&C(27Z>u&X{K?Cgn&QC^WK0|jN=R0US5M?I;m(4OBA&?p*}(BZD@NrL;J3%D8>5-fzNt0rgCWGF%*G zReJKM(#^MUr`lcFq`b(%t(u&=>vx4$Zglo z{Bm&U`O{9`@f}TLkWCPDuz##>@`3jkug1(_rk(|+2xRix#IdersGN%4Gdj0px=>a) zHHwYSYjoLpPtp1X>uZnMdCmR@A8~3(8_dP}t3@xlcjv10m~d`Z2wuIMEkxe24=(G2 zh>e&1|0nV^dySS_`LST^dB^>B>mE064(ZiLgNx$T==dzVz~}Ww@j}}Wf@EPbjr)Wd z^=}yC0;3~pKUVB%d72bU4wJ_-7H~sB_=rJvC$~#z%avi%pEp%9_ZHieWFy4E` zc0N=3aXOrI+mgfNW*lp!=^WAReA6K3^D-&_&pyO@Bf0NYVNMeWx^azR0oG{k8{Xc3 z&Ho=q=NZoC|F&_|C{?O8O0AkTic))1)ZTkl6-C7sdvqABy*F*`StE&vS$nrO5!7CZ zognx<_y2j3k+OqtuEEXj!-H{!6^$! zu^v5Xzatu3u|*emeD_zGeViZwf>G&-HBFv$xXj=wGEn@W^iYOv0ZhO0J0cglk56a; z3Pr{+666Ed)mP@8ND|wQfa4T98_}~8mf@R=x;T}SUrpO5yn*MPwV^Qw5ItIjDaRI^ zMXt*x+r5G(sI;=ZjppiUpvXFL6My5;=4Nzuy4&e&`cI~1WVFv9GquC5C3dF(cJhp;h z(jIG)7}+|Jef5x%Uf*cjdoRpj(WL14j_p^cLRKR*`n^ovb{an8BLN!~+bI*Cy_nvC zs3JZEb74v1pK?SMXOaq%0mI$Xop1MT%~(tGw4D0#+0%tkgnLzAoeb9%-)UQ=d zD|Irjo@=@1S$^_~>MXJ0(NjXXhwftjEX@q%Ec%JwP_MyekY;n7sKr(fje%MFD_ z&MGPCz%`+M3wwkv;A|u!EBFKKopfUF-i?e@s)~(jj9>9%wGRn@hB;ihdyGJz8^c)J zEtl66BDPmrD=ra>IRhyN*ZqJwxt}+|*Z6Uf`A0{M@r_QDMd0Udc{>ZneP$|*S!GwD zIIq!n%VHMN22uS7k?j;dLL{$NQ#tI2skl>uT?f;}_vqBZw0EiV1iM!T2bD#&9zkgMnW%c6gs>JDg|s#&rHKpT~3w{omu4ZkqGU*a4hvsvBOw8@gv z_ZZuKfMa6_ON`yY1mKYBiLEEv3*Q9&GN%Nngkhv~FlGx58|`S*3! zHA%Kij%nDc00O}?0TmKXOrfz7x;&fvm8$Q{-b>lpgYKi=~5qDa`GSg2SHETMLCINK2n(ksQlW4J<6bPdn6sRcJ!#&CKZYBO_&D^TE5+|lE7=-#cf(D_ z>!CujV7~S5J^rbEOn@Ft+1g!$yCQZCI~`WY#JoH+*xpXCk9gBJyAi-rX8WDR{JpF> zXW>4O=<&~$G2pgb0HzYKWjNP>Y6@OzG&YsTth6+>ELuZd1M+!bJ{~?6Ik9@7x(i`>NUa-~L}vwA)Y*CC zkc*{#&c@_EWX|=Q0?GTXs)jAy&PO^{sivd3H4Zo?ClVaNC`X&FWcgRJFLxwxS|ZbN zhO?iKlBzQDbXzXJ?hZ=HWynx`6fARV779}xO<$vIEE(Y;WkU)lM$v80mvJ8Obe!hBYa+XZU( z%Ay+;Si)=8Z7g4L-~ndz$M`0?q%}63;GSnq-%BTAopp`mm4dy+9b*(&C{MYg8=~KY zfn8f+0TMWdt6H6Hxz0~3IP9Uvy5o=42}4$t#L29AT2Mj_JC_HGV(NSdsuo+dl#U!@ z6ZRVy*I2MAWfMOs$V20!(e_O~&<+`9`2=5{sdYp>6>9xEAQ9l)bqgw(2_<7Cp)0sY zO+@}G>z~SCTURA;FC6?PDeX)cP24<3;};j;8%~R6-S?WSHm80 zZo2kee!xdlVYoN0xAOJ|y&rI3gz>&iobW_+1~-9L{3nD*uh^0<@UqrUx9GWB^DrP* zB@rD#D!J^g$lwTFfH^vkReM2R<@ahdY`%KcwYJi32?;yA=i%AS#u<{A7X(*`*#ZN! zQ%HdD$x`@oPLa(1Id9%PQdaZq%xgUJChq!80Ub%0AO8q|tunVSVfJ;*TB=HQk0qmg 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 Rp6CZ&# Date: Wed, 10 Oct 2018 13:51:55 -0500 Subject: [PATCH 102/278] Add info on release plan for Umbraco 8 to installer --- src/Umbraco.Web.UI.Client/src/installer/steps/user.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html index 5fb9d9bda8..1f916c6c00 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html @@ -1,5 +1,5 @@ - - + +


+
-

Install Umbraco 8 (alpha)

+

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

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

From a4903b9581c18f0685e6b4bfad643f3fec479d84 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 20:58:35 +0200 Subject: [PATCH 103/278] get jquery libraries to npm --- src/Umbraco.Web.UI.Client/bower.json | 16 +- src/Umbraco.Web.UI.Client/gulpfile.js | 33 +- src/Umbraco.Web.UI.Client/package-lock.json | 407 +++++++++++++------- src/Umbraco.Web.UI.Client/package.json | 5 + 4 files changed, 302 insertions(+), 159 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 38f3f67f81..77469f2ea7 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -18,11 +18,6 @@ "dependencies": { "rgrove-lazyload": "*", "bootstrap-social": "~4.8.0", - "jquery": "2.2.4", - "jquery-ui": "~1.12.0", - "jquery-migrate": "1.4.0", - "jquery-validate": "~1.17.0", - "jquery-validation-unobtrusive": "3.2.10", "angular-dynamic-locale": "~0.1.36", "ng-file-upload": "~12.2.13", "tinymce": "~4.7.1", @@ -48,10 +43,6 @@ "bower_components/moment/min/moment-with-locales.min.js", "bower_components/moment/locale/*.js" ], - "jquery": [ - "bower_components/jquery/dist/jquery.min.js", - "bower_components/jquery/dist/jquery.min.map" - ], "angular-dynamic-locale": [ "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" @@ -63,13 +54,8 @@ "tinymce": [ "bower_components/tinymce/tinymce.min.js" ], - "angular-i18n": "bower_components/angular-i18n/angular-locale_*.js", "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", - "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js", - "jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js", - "jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js", - "jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js", - "jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js" + "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js" } }, "devDependencies": { diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 24fb32d038..f71b37c7c8 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -235,7 +235,10 @@ gulp.task('dependencies', function () { }, { "name": "angular-i18n", - "src": ["./node_modules/angular-i18n/angular-i18n.js"], + "src": [ + "./node_modules/angular-i18n/angular-i18n.js", + "./node_modules/angular-i18n/angular-locale_*.js" + ], "base": "./node_modules/angular-i18n" }, { @@ -266,6 +269,34 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/flatpickr/dist" }, + { + "name": "jquery", + "src": [ + "./node_modules/jquery/dist/jquery.min.js", + "./node_modules/jquery/dist/jquery.min.map" + ], + "base": "./node_modules/jquery/dist" + }, + { + "name": "jquery-migrate", + "src": ["./node_modules/jquery-migrate/jquery-migrate.min.js"], + "base": "./node_modules/jquery-migrate" + }, + { + "name": "jquery-ui", + "src": ["./node_modules/jquery-ui/jquery-ui.min.js"], + "base": "./node_modules/jquery" + }, + { + "name": "jquery-validate", + "src": ["./node_modules/jquery-validation/dist/jquery.validate.min.js"], + "base": "./node_modules/jquery-validation/dist" + }, + { + "name": "jquery-validation-unobtrusive", + "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], + "base": "./node_modules/jquery-validation-unobtrusive/dist" + }, { "name": "signalr", "src": ["./node_modules/signalr/jquery.signalR.js"], diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 2bbe8d726d..a8b457b834 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -862,7 +862,7 @@ "acorn-jsx": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", - "integrity": "sha1-6OQeSOov4MiWdAYQq2pP/YrdIl4=", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { "acorn": "^5.0.3" @@ -884,7 +884,7 @@ "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha1-2J5ZmfeXh1Z0wH2H8mD8Qeg+jKk=", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -957,20 +957,53 @@ } }, "angular": { - "version": "1.3.20", - "resolved": "http://registry.npmjs.org/angular/-/angular-1.3.20.tgz", - "integrity": "sha1-sjo9fF5/mffZW5tNSbmsNVJpuu4=", - "dev": true + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", + "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" }, "angular-animate": { - "version": "1.3.20", - "resolved": "http://registry.npmjs.org/angular-animate/-/angular-animate-1.3.20.tgz", - "integrity": "sha1-0XB8cn+K0N8hxKLewgzX/ElFtSo=", - "dev": true + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", + "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" + }, + "angular-cookies": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", + "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" + }, + "angular-i18n": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", + "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" + }, + "angular-messages": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", + "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" + }, + "angular-route": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", + "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" + }, + "angular-sanitize": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", + "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" + }, + "angular-touch": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", + "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" + }, + "animejs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", + "integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA=" }, "ansi-colors": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -989,7 +1022,7 @@ "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha1-9zIHu4EgfXX9bIPxJa8m7qN4yjA=", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, "ansi-gray": { @@ -2173,6 +2206,16 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "clipboard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.0.tgz", + "integrity": "sha512-gXzHBlzEVqCk2b8Wpkil89S0WSMAX7eZho2zANX+EEEa9LMutGe9ICU+wHRzsH7cCHaCbUzj900P+AXOM0FE3A==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -2463,7 +2506,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -3516,6 +3559,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "depd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", @@ -3546,10 +3594,15 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, + "diff": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==" + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" @@ -4259,7 +4312,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -4403,7 +4456,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4413,19 +4466,19 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", - "integrity": "sha1-JTmY8goPgttdhmOFeZ2RKoOjZjQ=", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { "acorn": "^5.6.0", @@ -4441,7 +4494,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4450,7 +4503,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -5218,7 +5271,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -5361,23 +5414,21 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -5390,20 +5441,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5444,7 +5492,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -5459,14 +5507,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "glob": { @@ -5475,12 +5523,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-unicode": { @@ -5495,7 +5543,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "^2.1.0" } }, "ignore-walk": { @@ -5504,7 +5552,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "inflight": { @@ -5513,15 +5561,14 @@ "dev": true, "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5533,9 +5580,8 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -5548,25 +5594,22 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" } }, "minizlib": { @@ -5575,14 +5618,13 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5599,9 +5641,9 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, "node-pre-gyp": { @@ -5610,16 +5652,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.7", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -5628,8 +5670,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -5644,8 +5686,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -5654,17 +5696,16 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5676,9 +5717,8 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -5699,8 +5739,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -5721,10 +5761,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -5741,13 +5781,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -5756,7 +5796,7 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -5798,11 +5838,10 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -5811,7 +5850,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -5819,7 +5858,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -5834,13 +5873,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, "util-deprecate": { @@ -5855,7 +5894,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -6271,6 +6310,14 @@ "sparkles": "^1.0.0" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "got": { "version": "5.7.1", "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", @@ -6504,7 +6551,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", "dev": true, "requires": { "eslint": "^5.0.1", @@ -6711,6 +6758,18 @@ "vinyl-fs": "0.3.7" }, "dependencies": { + "angular": { + "version": "1.3.20", + "resolved": "http://registry.npmjs.org/angular/-/angular-1.3.20.tgz", + "integrity": "sha1-sjo9fF5/mffZW5tNSbmsNVJpuu4=", + "dev": true + }, + "angular-animate": { + "version": "1.3.20", + "resolved": "http://registry.npmjs.org/angular-animate/-/angular-animate-1.3.20.tgz", + "integrity": "sha1-0XB8cn+K0N8hxKLewgzX/ElFtSo=", + "dev": true + }, "ansi-regex": { "version": "0.2.1", "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", @@ -7479,6 +7538,14 @@ "requires": { "httpreq": ">=0.4.22", "underscore": "~1.7.0" + }, + "dependencies": { + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + } } }, "httpreq": { @@ -8143,7 +8210,7 @@ "is-path-in-cwd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha1-WsSLNF72dTOb1sekipEhELJBz1I=", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { "is-path-inside": "^1.0.0" @@ -8231,7 +8298,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -8366,6 +8433,38 @@ "logalot": "^2.0.0" } }, + "jquery": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", + "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=" + }, + "jquery-migrate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", + "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" + }, + "jquery-ui": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", + "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + }, + "jquery-validation": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.17.0.tgz", + "integrity": "sha512-XddiAwhGdWhcIJ+W3ri3KG8uTPMua4TPYuUIC8/E7lOyqdScG5xHuy9YishlKc0c/lIQai77EX7hxMdTSYCEjA==", + "requires": { + "jquery": "^1.7 || ^2.0 || ^3.1" + } + }, + "jquery-validation-unobtrusive": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.10.tgz", + "integrity": "sha512-z9ZBP/HslaGNKzFSpfLNJoFm2iqPJfE6CKM0H5e9LmKnYTFxErvCFQZomOLiTmLmZi8Wi/otW38cEXExVDha0w==", + "requires": { + "jquery": ">=1.8", + "jquery-validation": ">=1.16" + } + }, "js-base64": { "version": "2.4.9", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", @@ -8416,7 +8515,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify": { @@ -8500,7 +8599,7 @@ "karma": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.5.tgz", - "integrity": "sha1-NxDHoucbHEOTE/KDhG2I4E5PkYw=", + "integrity": "sha512-rECezBeY7mjzGUWhFlB7CvPHgkHJLXyUmWg+6vHCEsdWNUTnmiS6jRrIMcJEWgU2DUGZzGWG0bTRVky8fsDTOA==", "dev": true, "requires": { "bluebird": "^3.3.0", @@ -8535,7 +8634,7 @@ "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { "micromatch": "^3.1.4", @@ -8569,7 +8668,7 @@ "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha1-NW/04rDo5D4yLRijckYLvPOszSY=", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -9204,7 +9303,7 @@ "log4js": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz", - "integrity": "sha1-vzkC7/ZcaSPZzpz70ttUFg40AFo=", + "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==", "dev": true, "requires": { "amqplib": "^0.5.2", @@ -9475,7 +9574,7 @@ "mailgun-js": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.18.1.tgz", - "integrity": "sha1-7jmqGNe7WYpc6e3oSvtoHe/IprA=", + "integrity": "sha512-lvuMP14u24HS2uBsJEnzSyPMxzU2b99tQsIx1o6QNjqxjk8b3WvR+vq5oG1mjqz/IBYo+5gF+uSoDS0RkMVHmg==", "dev": true, "optional": true, "requires": { @@ -9745,7 +9844,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minimatch": { @@ -13505,7 +13604,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -14203,7 +14302,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14338,7 +14437,7 @@ "regexpp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha1-sqdTSoXKGwM7z1zp/45W1OB1U2U=", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", "dev": true }, "regexpu-core": { @@ -14708,6 +14807,11 @@ "commander": "~2.8.1" } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", @@ -14920,6 +15024,14 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "signalr": { + "version": "2.2.1", + "resolved": "http://registry.npmjs.org/signalr/-/signalr-2.2.1.tgz", + "integrity": "sha1-YRI0QP2iqa1799/KHhr2eFPB3Sw=", + "requires": { + "jquery": ">=1.6.4" + } + }, "slack-node": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz", @@ -14939,7 +15051,7 @@ "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" @@ -15159,7 +15271,7 @@ "socks-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha1-WTa/i3B6mTB5xvN9sgkYIb/6ZHM=", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", "dev": true, "requires": { "agent-base": "~4.2.0", @@ -15489,7 +15601,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15676,7 +15788,7 @@ }, "table": { "version": "4.0.3", - "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { @@ -15773,7 +15885,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15913,6 +16025,11 @@ "dev": true, "optional": true }, + "tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + }, "tiny-lr": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", @@ -16171,6 +16288,11 @@ "mime-types": "~2.1.18" } }, + "typeahead.js": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.10.5.tgz", + "integrity": "sha1-HZlxsPRNOF/q2/IsnzadtWKRLeE=" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -16217,10 +16339,9 @@ "dev": true }, "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", @@ -16371,13 +16492,13 @@ "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 9b5e2b6d49..ec40e64ece 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -17,6 +17,11 @@ "clipboard": "2.0.0", "diff": "3.4.0", "flatpickr": "4.5.2", + "jquery": "2.2.4", + "jquery-migrate": "1.4.0", + "jquery-ui": "1.12.1", + "jquery-validation": "1.17.0", + "jquery-validation-unobtrusive": "3.2.10", "npm": "^6.4.1", "signalr": "2.2.1", "typeahead.js": "0.10.5", From 4d13c631edb6d56e501b2344253372c06ee64532 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 21:29:42 +0200 Subject: [PATCH 104/278] aaand move some more --- src/Umbraco.Web.UI.Client/bower.json | 6 +--- src/Umbraco.Web.UI.Client/gulpfile.js | 28 +++++++++++++------ src/Umbraco.Web.UI.Client/package-lock.json | 31 +++++++++++++++++++++ src/Umbraco.Web.UI.Client/package.json | 3 ++ 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 77469f2ea7..66c5ca4827 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -17,15 +17,12 @@ ], "dependencies": { "rgrove-lazyload": "*", - "bootstrap-social": "~4.8.0", "angular-dynamic-locale": "~0.1.36", - "ng-file-upload": "~12.2.13", "tinymce": "~4.7.1", "codemirror": "~5.3.0", "angular-local-storage": "~0.7.1", "moment": "~2.10.3", "ace-builds": "~1.3.0", - "font-awesome": "~4.2", "angular-ui-sortable": "0.14.4" }, "install": { @@ -54,8 +51,7 @@ "tinymce": [ "bower_components/tinymce/tinymce.min.js" ], - "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js", - "ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js" + "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js" } }, "devDependencies": { diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index f71b37c7c8..6eaa01f822 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -157,14 +157,6 @@ gulp.task('dependencies', function () { { base: "./bower_components/tinymce/" }) .pipe(gulp.dest(root + targets.lib + "/tinymce")) ); - - //font-awesome - stream.add( - gulp.src(["./bower_components/font-awesome/fonts/*", - "./bower_components/font-awesome/css/font-awesome.min.css"], - { base: "./bower_components/font-awesome/" }) - .pipe(gulp.dest(root + targets.lib + "/font-awesome")) - ); // ace Editor stream.add( @@ -251,6 +243,11 @@ gulp.task('dependencies', function () { "src": ["./node_modules/animejs/anime.min.js"], "base": "./node_modules/animejs" }, + { + "name": "bootstrap-social", + "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], + "base": "./node_modules/bootstrap-social" + }, { "name": "clipboard", "src": ["./node_modules/clipboard/dist/clipboard.min.js"], @@ -269,6 +266,14 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/flatpickr/dist" }, + { + "name": "font-awesome", + "src": [ + "./node_modules/font-awesome/fonts/*", + "./node_modules/font-awesome/css/font-awesome.min.css" + ], + "base": "./node_modules/font-awesome" + }, { "name": "jquery", "src": [ @@ -285,7 +290,7 @@ gulp.task('dependencies', function () { { "name": "jquery-ui", "src": ["./node_modules/jquery-ui/jquery-ui.min.js"], - "base": "./node_modules/jquery" + "base": "./node_modules/jquery-ui" }, { "name": "jquery-validate", @@ -297,6 +302,11 @@ gulp.task('dependencies', function () { "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], "base": "./node_modules/jquery-validation-unobtrusive/dist" }, + { + "name": "ng-file-upload", + "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], + "base": "./node_modules/ng-file-upload/dist" + }, { "name": "signalr", "src": ["./node_modules/signalr/jquery.signalR.js"], diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index a8b457b834..bc7f94c6e0 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1733,6 +1733,27 @@ "hoek": "2.x.x" } }, + "bootstrap": { + "version": "3.3.7", + "resolved": "http://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", + "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + }, + "bootstrap-social": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/bootstrap-social/-/bootstrap-social-4.8.0.tgz", + "integrity": "sha1-ZtRj3JZtbbQH37mTNR1YxTSqLHo=", + "requires": { + "bootstrap": "~3", + "font-awesome": "~4.3" + }, + "dependencies": { + "font-awesome": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.3.0.tgz", + "integrity": "sha1-RO63kM35hmQnhvM/znhHZPGEHEA=" + } + } + }, "bower": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.4.tgz", @@ -5279,6 +5300,11 @@ } } }, + "font-awesome": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.2.0.tgz", + "integrity": "sha1-RzOKGgF9pr75XOLhsOF2go/Yreo=" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -10004,6 +10030,11 @@ "dev": true, "optional": true }, + "ng-file-upload": { + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz", + "integrity": "sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ=" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ec40e64ece..ea36b3577b 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -14,14 +14,17 @@ "angular-sanitize": "1.7.5", "angular-touch": "1.7.5", "animejs": "2.2.0", + "bootstrap-social": "4.8.0", "clipboard": "2.0.0", "diff": "3.4.0", "flatpickr": "4.5.2", + "font-awesome": "4.2.0", "jquery": "2.2.4", "jquery-migrate": "1.4.0", "jquery-ui": "1.12.1", "jquery-validation": "1.17.0", "jquery-validation-unobtrusive": "3.2.10", + "ng-file-upload": "12.2.13", "npm": "^6.4.1", "signalr": "2.2.1", "typeahead.js": "0.10.5", From 7e85235e20ea172cf9ce04e901dbb1a5a8506d04 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 10 Oct 2018 21:32:08 +0200 Subject: [PATCH 105/278] delete cssparser and less libraries --- .../lib/cssparser/cssparser.js | 5495 ----------------- .../lib/less/less-1.7.0.min.js | 16 - 2 files changed, 5511 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js delete mode 100644 src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js diff --git a/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js b/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js deleted file mode 100644 index 9903359454..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/cssparser/cssparser.js +++ /dev/null @@ -1,5495 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * emk - * Daniel Glazman - * L. David Baron - * Boris Zbarsky - * Mats Palmgren - * Christian Biesinger - * Jeff Walden - * Jonathon Jongsma , Collabora Ltd. - * Siraj Razick , Collabora Ltd. - * Daniel Glazman - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -var kCHARSET_RULE_MISSING_SEMICOLON = "Missing semicolon at the end of @charset rule"; -var kCHARSET_RULE_CHARSET_IS_STRING = "The charset in the @charset rule should be a string"; -var kCHARSET_RULE_MISSING_WS = "Missing mandatory whitespace after @charset"; -var kIMPORT_RULE_MISSING_URL = "Missing URL in @import rule"; -var kURL_EOF = "Unexpected end of stylesheet"; -var kURL_WS_INSIDE = "Multiple tokens inside a url() notation"; -var kVARIABLES_RULE_POSITION = "@variables rule invalid at this position in the stylesheet"; -var kIMPORT_RULE_POSITION = "@import rule invalid at this position in the stylesheet"; -var kNAMESPACE_RULE_POSITION = "@namespace rule invalid at this position in the stylesheet"; -var kCHARSET_RULE_CHARSET_SOF = "@charset rule invalid at this position in the stylesheet"; -var kUNKNOWN_AT_RULE = "Unknow @-rule"; - -/* FROM http://peter.sh/data/vendor-prefixed-css.php?js=1 */ - -var kCSS_VENDOR_VALUES = { - "-moz-box": {"webkit": "-webkit-box", "presto": "", "trident": "", "generic": "box" }, - "-moz-inline-box": {"webkit": "-webkit-inline-box", "presto": "", "trident": "", "generic": "inline-box" }, - "-moz-initial": {"webkit": "", "presto": "", "trident": "", "generic": "initial" }, - "flex": {"webkit": "-webkit-flex", "presto": "", "trident": "", "generic": "" }, - "inline-flex": {"webkit": "-webkit-inline-flex", "presto": "", "trident": "", "generic": "" }, - - "linear-gradient": {"webkit20110101":FilterLinearGradient, - "webkit": FilterLinearGradient, - "presto": FilterLinearGradient, - "trident": FilterLinearGradient, - "gecko1.9.2": FilterLinearGradient }, - "repeating-linear-gradient": {"webkit20110101":FilterLinearGradient, - "webkit": FilterLinearGradient, - "presto": FilterLinearGradient, - "trident": FilterLinearGradient, - "gecko1.9.2": FilterLinearGradient }, - - "radial-gradient": {"webkit20110101":FilterRadialGradient, - "webkit": FilterRadialGradient, - "presto": FilterRadialGradient, - "trident": FilterRadialGradient, - "gecko1.9.2": FilterRadialGradient }, - "repeating-radial-gradient": {"webkit20110101":FilterRadialGradient, - "webkit": FilterRadialGradient, - "presto": FilterRadialGradient, - "trident": FilterRadialGradient, - "gecko1.9.2": FilterRadialGradient } -}; - -var kCSS_PREFIXED_VALUE = [ - {"gecko": "-moz-box", "webkit": "-moz-box", "presto": "", "trident": "", "generic": "box"} -]; - -var kCSS_VENDOR_PREFIXES = -{"lastUpdate":1374677405,"properties":[ -{"gecko":"","webkit":"","presto":"","trident":"-ms-accelerator","status":"P"}, -{"gecko":"","webkit":"","presto":"-wap-accesskey","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-align-content","presto":"","trident":"","status":""}, -{"gecko":"align-items","webkit":"-webkit-align-items","presto":"","trident":"","status":""}, -{"gecko":"align-self","webkit":"-webkit-align-self","presto":"","trident":"","status":""}, -{"gecko":"animation","webkit":"-webkit-animation","presto":"","trident":"animation","status":"WD"}, -{"gecko":"animation-delay","webkit":"-webkit-animation-delay","presto":"","trident":"animation-delay","status":"WD"}, -{"gecko":"animation-direction","webkit":"-webkit-animation-direction","presto":"","trident":"animation-direction","status":"WD"}, -{"gecko":"animation-duration","webkit":"-webkit-animation-duration","presto":"","trident":"animation-duration","status":"WD"}, -{"gecko":"animation-fill-mode","webkit":"-webkit-animation-fill-mode","presto":"","trident":"animation-fill-mode","status":"ED"}, -{"gecko":"animation-iteration-count","webkit":"-webkit-animation-iteration-count","presto":"","trident":"animation-iteration-count","status":"WD"}, -{"gecko":"animation-name","webkit":"-webkit-animation-name","presto":"","trident":"animation-name","status":"WD"}, -{"gecko":"animation-play-state","webkit":"-webkit-animation-play-state","presto":"","trident":"animation-play-state","status":"WD"}, -{"gecko":"animation-timing-function","webkit":"-webkit-animation-timing-function","presto":"","trident":"animation-timing-function","status":"WD"}, -{"gecko":"","webkit":"-webkit-app-region","presto":"","trident":"","status":""}, -{"gecko":"-moz-appearance","webkit":"-webkit-appearance","presto":"","trident":"","status":"CR"}, -{"gecko":"","webkit":"-webkit-aspect-ratio","presto":"","trident":"","status":""}, -{"gecko":"backface-visibility","webkit":"-webkit-backface-visibility","presto":"","trident":"backface-visibility","status":"WD"}, -{"gecko":"","webkit":"-webkit-background-blend-mode","presto":"","trident":"","status":""}, -{"gecko":"background-clip","webkit":"-webkit-background-clip","presto":"background-clip","trident":"background-clip","status":"WD"}, -{"gecko":"","webkit":"-webkit-background-composite","presto":"","trident":"","status":""}, -{"gecko":"-moz-background-inline-policy","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"background-origin","webkit":"-webkit-background-origin","presto":"background-origin","trident":"background-origin","status":"WD"}, -{"gecko":"","webkit":"background-position-x","presto":"","trident":"-ms-background-position-x","status":""}, -{"gecko":"","webkit":"background-position-y","presto":"","trident":"-ms-background-position-y","status":""}, -{"gecko":"background-size","webkit":"-webkit-background-size","presto":"background-size","trident":"background-size","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-behavior","status":""}, -{"gecko":"-moz-binding","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-blend-mode","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-block-progression","status":""}, -{"gecko":"","webkit":"-webkit-border-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-color","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-style","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-after-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-color","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-style","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-before-width","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-bottom-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-bottom-left-radius","webkit":"-webkit-border-bottom-left-radius","presto":"border-bottom-left-radius","trident":"border-bottom-left-radius","status":"WD"}, -{"gecko":"border-bottom-right-radius","webkit":"-webkit-border-bottom-right-radius","presto":"border-bottom-right-radius","trident":"border-bottom-right-radius","status":"WD"}, -{"gecko":"-moz-border-end","webkit":"-webkit-border-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-color","webkit":"-webkit-border-end-color","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-style","webkit":"-webkit-border-end-style","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-end-width","webkit":"-webkit-border-end-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-border-fit","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-border-horizontal-spacing","presto":"","trident":"","status":""}, -{"gecko":"border-image","webkit":"-webkit-border-image","presto":"-o-border-image","trident":"","status":"WD"}, -{"gecko":"-moz-border-left-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-radius","webkit":"-webkit-border-radius","presto":"border-radius","trident":"border-radius","status":"WD"}, -{"gecko":"-moz-border-right-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-border-start","webkit":"-webkit-border-start","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-color","webkit":"-webkit-border-start-color","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-style","webkit":"-webkit-border-start-style","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-start-width","webkit":"-webkit-border-start-width","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-border-top-colors","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"border-top-left-radius","webkit":"-webkit-border-top-left-radius","presto":"border-top-left-radius","trident":"border-top-left-radius","status":"WD"}, -{"gecko":"border-top-right-radius","webkit":"-webkit-border-top-right-radius","presto":"border-top-right-radius","trident":"border-top-right-radius","status":"WD"}, -{"gecko":"","webkit":"-webkit-border-vertical-spacing","presto":"","trident":"","status":""}, -{"gecko":"-moz-box-align","webkit":"-webkit-box-align","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-decoration-break","presto":"box-decoration-break","trident":"","status":"WD"}, -{"gecko":"-moz-box-direction","webkit":"-webkit-box-direction","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-flex","webkit":"-webkit-box-flex","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-flex-group","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-lines","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-ordinal-group","webkit":"-webkit-box-ordinal-group","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-orient","webkit":"-webkit-box-orient","presto":"","trident":"","status":"WD"}, -{"gecko":"-moz-box-pack","webkit":"-webkit-box-pack","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-box-reflect","presto":"","trident":"","status":""}, -{"gecko":"box-shadow","webkit":"-webkit-box-shadow","presto":"box-shadow","trident":"box-shadow","status":"WD"}, -{"gecko":"-moz-box-sizing","webkit":"-webkit-box-sizing","presto":"box-sizing","trident":"box-sizing","status":"CR"}, -{"gecko":"caption-side","webkit":"-epub-caption-side","presto":"caption-side","trident":"caption-side","status":""}, -{"gecko":"clip-path","webkit":"-webkit-clip-path","presto":"clip-path","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-color-correction","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-axis","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-column-break-inside","presto":"","trident":"","status":""}, -{"gecko":"-moz-column-count","webkit":"-webkit-column-count","presto":"column-count","trident":"column-count","status":"CR"}, -{"gecko":"-moz-column-fill","webkit":"","presto":"column-fill","trident":"column-fill","status":"CR"}, -{"gecko":"-moz-column-gap","webkit":"-webkit-column-gap","presto":"column-gap","trident":"column-gap","status":"CR"}, -{"gecko":"","webkit":"-webkit-column-progression","presto":"","trident":"","status":""}, -{"gecko":"-moz-column-rule","webkit":"-webkit-column-rule","presto":"column-rule","trident":"column-rule","status":"CR"}, -{"gecko":"-moz-column-rule-color","webkit":"-webkit-column-rule-color","presto":"column-rule-color","trident":"column-rule-color","status":"CR"}, -{"gecko":"-moz-column-rule-style","webkit":"-webkit-column-rule-style","presto":"column-rule-style","trident":"column-rule-style","status":"CR"}, -{"gecko":"-moz-column-rule-width","webkit":"-webkit-column-rule-width","presto":"column-rule-width","trident":"column-rule-width","status":"CR"}, -{"gecko":"","webkit":"-webkit-column-span","presto":"column-span","trident":"column-span","status":"CR"}, -{"gecko":"-moz-column-width","webkit":"-webkit-column-width","presto":"column-width","trident":"column-width","status":"CR"}, -{"gecko":"-moz-columns","webkit":"-webkit-columns","presto":"columns","trident":"columns","status":"CR"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-chaining","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-limit-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-points","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zoom-snap-type","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-content-zooming","status":""}, -{"gecko":"","webkit":"-webkit-cursor-visibility","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-dashboard-region","presto":"-apple-dashboard-region","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-device-pixel-ratio","trident":"","status":""}, -{"gecko":"filter","webkit":"-webkit-filter","presto":"filter","trident":"-ms-filter","status":""}, -{"gecko":"flex","webkit":"-webkit-flex","presto":"","trident":"-ms-flex","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-align","status":""}, -{"gecko":"flex-basis","webkit":"-webkit-flex-basis","presto":"","trident":"","status":""}, -{"gecko":"flex-direction","webkit":"-webkit-flex-direction","presto":"","trident":"-ms-flex-direction","status":""}, -{"gecko":"","webkit":"-webkit-flex-flow","presto":"","trident":"","status":""}, -{"gecko":"flex-grow","webkit":"-webkit-flex-grow","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-order","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-flex-pack","status":""}, -{"gecko":"flex-shrink","webkit":"-webkit-flex-shrink","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-flex-wrap","presto":"","trident":"-ms-flex-wrap","status":""}, -{"gecko":"-moz-float-edge","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-flow-from","presto":"","trident":"-ms-flow-from","status":""}, -{"gecko":"","webkit":"-webkit-flow-into","presto":"","trident":"-ms-flow-into","status":""}, -{"gecko":"","webkit":"","presto":"-o-focus-opacity","trident":"","status":""}, -{"gecko":"-moz-font-feature-settings","webkit":"-webkit-font-feature-settings","presto":"","trident":"font-feature-settings","status":""}, -{"gecko":"font-kerning","webkit":"-webkit-font-kerning","presto":"","trident":"","status":""}, -{"gecko":"-moz-font-language-override","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-font-size-delta","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-font-smoothing","presto":"","trident":"","status":""}, -{"gecko":"font-variant-ligatures","webkit":"-webkit-font-variant-ligatures","presto":"","trident":"","status":""}, -{"gecko":"-moz-force-broken-image-icon","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-columns","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-flow","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-auto-rows","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-column","presto":"","trident":"-ms-grid-column","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-align","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-column-span","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-columns","status":"WD"}, -{"gecko":"","webkit":"-webkit-grid-definition-columns","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-definition-rows","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-end","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-grid-row","presto":"","trident":"-ms-grid-row","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-align","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-row-span","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-grid-rows","status":"WD"}, -{"gecko":"","webkit":"-webkit-grid-start","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-high-contrast-adjust","status":""}, -{"gecko":"","webkit":"-webkit-highlight","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-character","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-chars","status":""}, -{"gecko":"","webkit":"-webkit-hyphenate-limit-lines","presto":"","trident":"-ms-hyphenate-limit-lines","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-hyphenate-limit-zone","status":""}, -{"gecko":"-moz-hyphens","webkit":"-epub-hyphens","presto":"","trident":"-ms-hyphens","status":"WD"}, -{"gecko":"-moz-image-region","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"ime-mode","webkit":"","presto":"","trident":"-ms-ime-mode","status":""}, -{"gecko":"","webkit":"","presto":"-wap-input-format","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-input-required","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-interpolation-mode","status":""}, -{"gecko":"","webkit":"","presto":"-xv-interpret-as","trident":"","status":""}, -{"gecko":"justify-content","webkit":"-webkit-justify-content","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-flow","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-char","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-line","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-mode","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-layout-grid-type","status":""}, -{"gecko":"","webkit":"-webkit-line-align","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-box-contain","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-break","presto":"","trident":"line-break","status":""}, -{"gecko":"","webkit":"-webkit-line-clamp","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-grid","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-line-snap","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-link","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-link-source","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-locale","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-logical-height","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-after-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-before","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-before-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-bottom-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-margin-collapse","presto":"","trident":"","status":""}, -{"gecko":"-moz-margin-end","webkit":"-webkit-margin-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-margin-start","webkit":"-webkit-margin-start","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-margin-top-collapse","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-marquee-dir","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee-direction","presto":"","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-increment","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-wap-marquee-loop","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-repetition","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-marquee-speed","presto":"-wap-marquee-speed","trident":"","status":"WD"}, -{"gecko":"","webkit":"-webkit-marquee-style","presto":"-wap-marquee-style","trident":"","status":"WD"}, -{"gecko":"mask","webkit":"-webkit-mask","presto":"mask","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-outset","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-repeat","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-slice","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-source","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-box-image-width","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-clip","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-composite","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-image","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-origin","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-position-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-repeat-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-mask-size","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-max-logical-height","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-max-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-min-logical-height","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-min-logical-width","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"","presto":"-o-mini-fold","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-nbsp-mode","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"","presto":"-o-object-fit","trident":"","status":"ED"}, -{"gecko":"","webkit":"","presto":"-o-object-position","trident":"","status":"ED"}, -{"gecko":"opacity","webkit":"-webkit-opacity","presto":"opacity","trident":"opacity","status":"WD"}, -{"gecko":"order","webkit":"-webkit-order","presto":"","trident":"","status":""}, -{"gecko":"-moz-orient","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-outline-radius","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-bottomleft","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-bottomright","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-topleft","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-outline-radius-topright","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-overflow-scrolling","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-overflow-style","status":"CR"}, -{"gecko":"overflow-x","webkit":"overflow-x","presto":"overflow-x","trident":"-ms-overflow-x","status":"WD"}, -{"gecko":"overflow-y","webkit":"overflow-y","presto":"overflow-y","trident":"-ms-overflow-y","status":"WD"}, -{"gecko":"","webkit":"-webkit-padding-after","presto":"","trident":"","status":"ED"}, -{"gecko":"","webkit":"-webkit-padding-before","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-padding-end","webkit":"-webkit-padding-end","presto":"","trident":"","status":"ED"}, -{"gecko":"-moz-padding-start","webkit":"-webkit-padding-start","presto":"","trident":"","status":"ED"}, -{"gecko":"perspective","webkit":"-webkit-perspective","presto":"","trident":"perspective","status":"WD"}, -{"gecko":"perspective-origin","webkit":"-webkit-perspective-origin","presto":"","trident":"perspective-origin","status":"WD"}, -{"gecko":"","webkit":"-webkit-perspective-origin-x","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-perspective-origin-y","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-phonemes","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-print-color-adjust","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-progress-appearance","status":""}, -{"gecko":"","webkit":"-webkit-region-break-after","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-break-before","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-break-inside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-region-fragment","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-rtl-ordering","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-ruby-position","presto":"","trident":"ruby-position","status":"CR"}, -{"gecko":"-moz-script-level","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-script-min-size","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"-moz-script-size-multiplier","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-chaining","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-x-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-max","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-limit-y-min","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-rails","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-x","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-points-y","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-type","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-x","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-snap-y","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-scroll-translation","status":""}, -{"gecko":"","webkit":"","presto":"scrollbar-arrow-color","trident":"-ms-scrollbar-arrow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-base-color","trident":"-ms-scrollbar-base-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-darkshadow-color","trident":"-ms-scrollbar-darkshadow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-face-color","trident":"-ms-scrollbar-face-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-highlight-color","trident":"-ms-scrollbar-highlight-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-shadow-color","trident":"-ms-scrollbar-shadow-color","status":"P"}, -{"gecko":"","webkit":"","presto":"scrollbar-track-color","trident":"-ms-scrollbar-track-color","status":"P"}, -{"gecko":"","webkit":"-webkit-shape-inside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-margin","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-outside","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-shape-padding","presto":"","trident":"","status":""}, -{"gecko":"-moz-stack-sizing","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-svg-shadow","presto":"","trident":"","status":""}, -{"gecko":"-moz-tab-size","webkit":"tab-size","presto":"-o-tab-size","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-o-table-baseline","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-tap-highlight-color","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-text-align-last","webkit":"-webkit-text-align-last","presto":"","trident":"-ms-text-align-last","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-text-autospace","status":"WD"}, -{"gecko":"-moz-text-blink","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-combine","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-color","webkit":"-webkit-text-decoration-color","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-line","webkit":"-webkit-text-decoration-line","presto":"","trident":"","status":""}, -{"gecko":"-moz-text-decoration-style","webkit":"-webkit-text-decoration-style","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-decorations-in-effect","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis-color","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-emphasis-position","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-epub-text-emphasis-style","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-fill-color","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-justify","presto":"","trident":"-ms-text-justify","status":"WD"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-text-kashida-space","status":"P"}, -{"gecko":"","webkit":"-epub-text-orientation","presto":"","trident":"","status":""}, -{"gecko":"text-overflow","webkit":"text-overflow","presto":"text-overflow","trident":"-ms-text-overflow","status":"WD"}, -{"gecko":"","webkit":"-webkit-text-security","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-text-size-adjust","webkit":"","presto":"","trident":"","status":""}, -{"gecko":"","webkit":"-webkit-text-stroke","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-stroke-color","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-text-stroke-width","presto":"","trident":"","status":"P"}, -{"gecko":"text-transform","webkit":"-epub-text-transform","presto":"text-transform","trident":"text-transform","status":""}, -{"gecko":"","webkit":"-webkit-text-underline-position","presto":"","trident":"-ms-text-underline-position","status":"P"}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-touch-action","status":""}, -{"gecko":"","webkit":"-webkit-touch-callout","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-transform","webkit":"-webkit-transform","presto":"-o-transform","trident":"transform","status":"WD"}, -{"gecko":"transform-origin","webkit":"-webkit-transform-origin","presto":"-o-transform-origin","trident":"transform-origin","status":"WD"}, -{"gecko":"","webkit":"-webkit-transform-origin-x","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-transform-origin-y","presto":"","trident":"","status":"P"}, -{"gecko":"","webkit":"-webkit-transform-origin-z","presto":"","trident":"","status":"P"}, -{"gecko":"transform-style","webkit":"-webkit-transform-style","presto":"","trident":"transform-style","status":"WD"}, -{"gecko":"transition","webkit":"-webkit-transition","presto":"-o-transition","trident":"transition","status":"WD"}, -{"gecko":"transition-delay","webkit":"-webkit-transition-delay","presto":"-o-transition-delay","trident":"transition-delay","status":"WD"}, -{"gecko":"transition-duration","webkit":"-webkit-transition-duration","presto":"-o-transition-duration","trident":"transition-duration","status":"WD"}, -{"gecko":"transition-property","webkit":"-webkit-transition-property","presto":"-o-transition-property","trident":"transition-property","status":"WD"}, -{"gecko":"transition-timing-function","webkit":"-webkit-transition-timing-function","presto":"-o-transition-timing-function","trident":"transition-timing-function","status":"WD"}, -{"gecko":"","webkit":"-webkit-user-drag","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-focus","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-input","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-modify","webkit":"-webkit-user-modify","presto":"","trident":"","status":"P"}, -{"gecko":"-moz-user-select","webkit":"-webkit-user-select","presto":"","trident":"-ms-user-select","status":"P"}, -{"gecko":"","webkit":"","presto":"-xv-voice-balance","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-duration","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-pitch","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-pitch-range","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-rate","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-stress","trident":"","status":""}, -{"gecko":"","webkit":"","presto":"-xv-voice-volume","trident":"","status":""}, -{"gecko":"-moz-window-shadow","webkit":"","presto":"","trident":"","status":"P"}, -{"gecko":"word-break","webkit":"-epub-word-break","presto":"","trident":"-ms-word-break","status":"WD"}, -{"gecko":"word-wrap","webkit":"word-wrap","presto":"word-wrap","trident":"-ms-word-wrap","status":"WD"}, -{"gecko":"","webkit":"-webkit-wrap-flow","presto":"","trident":"-ms-wrap-flow","status":""}, -{"gecko":"","webkit":"","presto":"","trident":"-ms-wrap-margin","status":""}, -{"gecko":"","webkit":"-webkit-wrap-through","presto":"","trident":"-ms-wrap-through","status":""}, -{"gecko":"writing-mode","webkit":"-epub-writing-mode","presto":"writing-mode","trident":"-ms-writing-mode","status":"ED"}, -{"gecko":"","webkit":"zoom","presto":"","trident":"-ms-zoom","status":""}]}; - -var PrefixHelper = { - - mVENDOR_PREFIXES: null, - - kEXPORTS_FOR_GECKO: true, - kEXPORTS_FOR_WEBKIT: true, - kEXPORTS_FOR_PRESTO: true, - kEXPORTS_FOR_TRIDENT: true, - - cleanPrefixes: function() - { - this.mVENDOR_PREFIXES = null; - }, - - prefixesForProperty: function(aProperty) - { - if (!this.mVENDOR_PREFIXES) { - - this.mVENDOR_PREFIXES = {}; - for (var i = 0; i < kCSS_VENDOR_PREFIXES.properties.length; i++) { - var p = kCSS_VENDOR_PREFIXES.properties[i]; - if (p.gecko && (p.webkit || p.presto || p.trident)) { - var o = {}; - if (this.kEXPORTS_FOR_GECKO) o[p.gecko] = true; - if (this.kEXPORTS_FOR_WEBKIT && p.webkit) o[p.webkit] = true; - if (this.kEXPORTS_FOR_PRESTO && p.presto) o[p.presto] = true; - if (this.kEXPORTS_FOR_TRIDENT && p.trident) o[p.trident] = true; - this.mVENDOR_PREFIXES[p.gecko] = []; - for (var j in o) - this.mVENDOR_PREFIXES[p.gecko].push(j) - } - } - } - if (aProperty in this.mVENDOR_PREFIXES) - return this.mVENDOR_PREFIXES[aProperty].sort(); - return null; - } -}; - -function ParseURL(buffer) { - var result = { }; - result.protocol = ""; - result.user = ""; - result.password = ""; - result.host = ""; - result.port = ""; - result.path = ""; - result.query = ""; - - var section = "PROTOCOL"; - var start = 0; - var wasSlash = false; - - while(start < buffer.length) { - if(section == "PROTOCOL") { - if(buffer.charAt(start) == ':') { - section = "AFTER_PROTOCOL"; - start++; - } else if(buffer.charAt(start) == '/' && result.protocol.length() == 0) { - section = PATH; - } else { - result.protocol += buffer.charAt(start++); - } - } else if(section == "AFTER_PROTOCOL") { - if(buffer.charAt(start) == '/') { - if(!wasSlash) { - wasSlash = true; - } else { - wasSlash = false; - section = "USER"; - } - start ++; - } else { - throw new ParseException("Protocol shell be separated with 2 slashes"); - } - } else if(section == "USER") { - if(buffer.charAt(start) == '/') { - result.host = result.user; - result.user = ""; - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - result.host = result.user; - result.user = ""; - section = "QUERY"; - start++; - } else if(buffer.charAt(start) == ':') { - section = "PASSWORD"; - start++; - } else if(buffer.charAt(start) == '@') { - section = "HOST"; - start++; - } else { - result.user += buffer.charAt(start++); - } - } else if(section == "PASSWORD") { - if(buffer.charAt(start) == '/') { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - section = "QUERY"; - start ++; - } else if(buffer.charAt(start) == '@') { - section = "HOST"; - start++; - } else { - result.password += buffer.charAt(start++); - } - } else if(section == "HOST") { - if(buffer.charAt(start) == '/') { - section = "PATH"; - } else if(buffer.charAt(start) == ':') { - section = "PORT"; - start++; - } else if(buffer.charAt(start) == '?') { - section = "QUERY"; - start++; - } else { - result.host += buffer.charAt(start++); - } - } else if(section == "PORT") { - if(buffer.charAt(start) == '/') { - section = "PATH"; - } else if(buffer.charAt(start) == '?') { - section = "QUERY"; - start++; - } else { - result.port += buffer.charAt(start++); - } - } else if(section == "PATH") { - if(buffer.charAt(start) == '?') { - section = "QUERY"; - start ++; - } else { - result.path += buffer.charAt(start++); - } - } else if(section == "QUERY") { - result.query += buffer.charAt(start++); - } - } - - if(section == "PROTOCOL") { - result.host = result.protocol; - result.protocol = "http"; - } else if(section == "AFTER_PROTOCOL") { - throw new ParseException("Invalid url"); - } else if(section == "USER") { - result.host = result.user; - result.user = ""; - } else if(section == "PASSWORD") { - result.host = result.user; - result.port = result.password; - result.user = ""; - result.password = ""; - } - - return result; -} - -function ParseException(description) { - this.description = description; -} - -function CountLF(s) -{ - var nCR = s.match( /\n/g ); - return nCR ? nCR.length + 1 : 1; -} - -function DisposablePartialParsing(aStringToParse, aMethodName) -{ - var parser = new CSSParser(); - parser._init(); - parser.mPreserveWS = false; - parser.mPreserveComments = false; - parser.mPreservedTokens = []; - parser.mScanner.init(aStringToParse); - - return parser[aMethodName](); -} - -function FilterLinearGradient(aValue, aEngine) -{ - var d = DisposablePartialParsing(aValue, "parseBackgroundImages"); - if (!d) - return null; - var g = d[0]; - if (!g.value) - return null; - - var str = ""; - var position = ("position" in g.value) ? g.value.position.toLowerCase() : ""; - var angle = ("angle" in g.value) ? g.value.angle.toLowerCase() : ""; - - if ("webkit20110101" == aEngine) { - var cancelled = false; - str = "-webkit-gradient(linear, "; - // normalize angle - if (angle) { - var match = angle.match(/^([0-9\-\.\\+]+)([a-z]*)/); - var angle = parseFloat(match[1]); - var unit = match[2]; - switch (unit) { - case "grad": angle = angle * 90 / 100; break; - case "rad": angle = angle * 180 / Math.PI; break; - default: break; - } - while (angle < 0) - angle += 360; - while (angle >= 360) - angle -= 360; - } - // get startpoint w/o keywords - var startpoint = []; - var endpoint = []; - if (position != "") { - if (position == "center") - position = "center center"; - startpoint = position.split(" "); - if (angle == "" && angle != 0) { - // no angle, then we just turn the point 180 degrees around center - switch (startpoint[0]) { - case "left": endpoint.push("right"); break; - case "center": endpoint.push("center"); break; - case "right": endpoint.push("left"); break; - default: { - var match = startpoint[0].match(/^([0-9\-\.\\+]+)([a-z]*)/); - var v = parseFloat(match[0]); - var unit = match[1]; - if (unit == "%") { - endpoint.push((100-v) + "%"); - } - else - cancelled = true; - } - break; - } - if (!cancelled) - switch (startpoint[1]) { - case "top": endpoint.push("bottom"); break; - case "center": endpoint.push("center"); break; - case "bottom": endpoint.push("top"); break; - default: { - var match = startpoint[1].match(/^([0-9\-\.\\+]+)([a-z]*)/); - var v = parseFloat(match[0]); - var unit = match[1]; - if (unit == "%") { - endpoint.push((100-v) + "%"); - } - else - cancelled = true; - } - break; - } - } - else { - switch (angle) { - case 0: endpoint.push("right"); endpoint.push(startpoint[1]); break; - case 90: endpoint.push(startpoint[0]); endpoint.push("top"); break; - case 180: endpoint.push("left"); endpoint.push(startpoint[1]); break; - case 270: endpoint.push(startpoint[0]); endpoint.push("bottom"); break; - default: cancelled = true; break; - } - } - } - else { - // no position defined, we accept only vertical and horizontal - if (angle == "") - angle = 270; - switch (angle) { - case 0: startpoint= ["left", "center"]; endpoint = ["right", "center"]; break; - case 90: startpoint= ["center", "bottom"]; endpoint = ["center", "top"]; break; - case 180: startpoint= ["right", "center"]; endpoint = ["left", "center"]; break; - case 270: startpoint= ["center", "top"]; endpoint = ["center", "bottom"]; break; - default: cancelled = true; break; - } - } - - if (cancelled) - return ""; - - str += startpoint.join(" ") + ", " + endpoint.join(" "); - if (!g.value.stops[0].position) - g.value.stops[0].position = "0%"; - if (!g.value.stops[g.value.stops.length-1].position) - g.value.stops[g.value.stops.length-1].position = "100%"; - var current = 0; - for (var i = 0; i < g.value.stops.length && !cancelled; i++) { - var s = g.value.stops[i]; - if (s.position) { - if (s.position.indexOf("%") == -1) { - cancelled = true; - break; - } - } - else { - var j = i + 1; - while (j < g.value.stops.length && !g.value.stops[j].position) - j++; - var inc = parseFloat(g.value.stops[j].position) - current; - for (var k = i; k < j; k++) { - g.value.stops[k].position = (current + inc * (k - i + 1) / (j - i + 1)) + "%"; - } - } - current = parseFloat(s.position); - str += ", color-stop(" + (parseFloat(current) / 100) + ", " + s.color + ")"; - } - - if (cancelled) - return ""; - } - else { - str = (g.value.isRepeating ? "repeating-" : "") + "linear-gradient("; - if (angle || position) - str += (angle ? angle : position) + ", "; - - for (var i = 0; i < g.value.stops.length; i++) { - var s = g.value.stops[i]; - str += s.color - + (s.position ? " " + s.position : "") - + ((i != g.value.stops.length -1) ? ", " : ""); - } - } - str += ")"; - - switch (aEngine) { - case "webkit": str = "-webkit-" + str; break; - case "gecko1.9.2": str = "-moz-" + str; break; - case "presto": str = "-o-" + str; break; - case "trident": str = "-ms-" + str; break; - default: break; - } - return str; -} - -function FilterRadialGradient(aValue, aEngine) -{ - var d = DisposablePartialParsing(aValue, "parseBackgroundImages"); - if (!d) - return null; - var g = d[0]; - if (!g.value) - return null; - - // oh come on, this is now so painful to deal with ; no way I'm going to implement this - if ("webkit20110101" == aEngine) - return null; - - var str = (g.value.isRepeating ? "repeating-" : "") + "radial-gradient("; - var shape = ("shape" in g.value) ? g.value.shape : ""; - var extent = ("extent" in g.value) ? g.value.extent : ""; - var lengths = ""; - switch (g.value.positions.length) { - case 1: - lengths = g.value.positions[0] + " " + g.value.positions[0]; - break; - case 2: - lengths = g.value.positions[0] + " " + g.value.positions[1]; - break; - default: - break; - } - var at = g.value.at; - - str += (at ? at + ", " : "") - + ((shape || extent || at) - ? (shape ? shape + " " : "") - + (extent ? extent + " " : "") - + (lengths ? lengths + " " : "") - + ", " - : ""); - for (var i = 0; i < g.value.stops.length; i++) { - var s = g.value.stops[i]; - str += s.color - + (s.position ? " " + s.position : "") - + ((i != g.value.stops.length -1) ? ", " : ""); - } - str += ")"; - - switch (aEngine) { - case "webkit": str = "-webkit-" + str; break; - case "gecko1.9.2": str = "-moz-" + str; break; - case "presto": str = "-o-" + str; break; - case "trident": str = "-ms-" + str; break; - default: break; - } - return str; -} - -var CSS_ESCAPE = '\\'; - -var IS_HEX_DIGIT = 1; -var START_IDENT = 2; -var IS_IDENT = 4; -var IS_WHITESPACE = 8; - -var W = IS_WHITESPACE; -var I = IS_IDENT; -var S = START_IDENT; -var SI = IS_IDENT|START_IDENT; -var XI = IS_IDENT |IS_HEX_DIGIT; -var XSI = IS_IDENT|START_IDENT|IS_HEX_DIGIT; - -function CSSScanner(aString) -{ - this.init(aString); -} - -CSSScanner.prototype = { - - kLexTable: [ - // TAB LF FF CR - 0, 0, 0, 0, 0, 0, 0, 0, 0, W, W, 0, W, W, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // SPC ! " # $ % & ' ( ) * + , - . / - W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, I, 0, 0, - // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? - XI, XI, XI, XI, XI, XI, XI, XI, XI, XI, 0, 0, 0, 0, 0, 0, - // @ A B C D E F G H I J K L M N O - 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI, - // P Q R S T U V W X Y Z [ \ ] ^ _ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, S, 0, 0, SI, - // ` a b c d e f g h i j k l m n o - 0, XSI,XSI,XSI,XSI,XSI,XSI,SI, SI, SI, SI, SI, SI, SI, SI, SI, - // p q r s t u v w x y z { | } ~ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, 0, 0, 0, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - // ¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ - 0, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // à á â ã ä å æ ç è é ê ë ì í î ï - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, - // ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ - SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI, SI - ], - - kHexValues: { - "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, - "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15 - }, - - mString : "", - mPos : 0, - mPreservedPos : [], - - init: function(aString) { - this.mString = aString; - this.mPos = 0; - this.mPreservedPos = []; - }, - - getCurrentPos: function() { - return this.mPos; - }, - - getAlreadyScanned: function() - { - return this.mString.substr(0, this.mPos); - }, - - preserveState: function() { - this.mPreservedPos.push(this.mPos); - }, - - restoreState: function() { - if (this.mPreservedPos.length) { - this.mPos = this.mPreservedPos.pop(); - } - }, - - forgetState: function() { - if (this.mPreservedPos.length) { - this.mPreservedPos.pop(); - } - }, - - read: function() { - if (this.mPos < this.mString.length) - return this.mString.charAt(this.mPos++); - return -1; - }, - - peek: function() { - if (this.mPos < this.mString.length) - return this.mString.charAt(this.mPos); - return -1; - }, - - isHexDigit: function(c) { - var code = c.charCodeAt(0); - return (code < 256 && (this.kLexTable[code] & IS_HEX_DIGIT) != 0); - }, - - isIdentStart: function(c) { - var code = c.charCodeAt(0); - return (code >= 256 || (this.kLexTable[code] & START_IDENT) != 0); - }, - - startsWithIdent: function(aFirstChar, aSecondChar) { - var code = aFirstChar.charCodeAt(0); - return this.isIdentStart(aFirstChar) || - (aFirstChar == "-" && this.isIdentStart(aSecondChar)); - }, - - isIdent: function(c) { - var code = c.charCodeAt(0); - return (code >= 256 || (this.kLexTable[code] & IS_IDENT) != 0); - }, - - pushback: function() { - this.mPos--; - }, - - nextHexValue: function() { - var c = this.read(); - if (c == -1 || !this.isHexDigit(c)) - return new jscsspToken(jscsspToken.NULL_TYPE, null); - var s = c; - c = this.read(); - while (c != -1 && this.isHexDigit(c)) { - s += c; - c = this.read(); - } - if (c != -1) - this.pushback(); - return new jscsspToken(jscsspToken.HEX_TYPE, s); - }, - - gatherEscape: function() { - var c = this.peek(); - if (c == -1) - return ""; - if (this.isHexDigit(c)) { - var code = 0; - for (var i = 0; i < 6; i++) { - c = this.read(); - if (this.isHexDigit(c)) - code = code * 16 + this.kHexValues[c.toLowerCase()]; - else if (!this.isHexDigit(c) && !this.isWhiteSpace(c)) { - this.pushback(); - break; - } - else - break; - } - if (i == 6) { - c = this.peek(); - if (this.isWhiteSpace(c)) - this.read(); - } - return String.fromCharCode(code); - } - c = this.read(); - if (c != "\n") - return c; - return ""; - }, - - gatherIdent: function(c) { - var s = ""; - if (c == CSS_ESCAPE) - s += this.gatherEscape(); - else - s += c; - c = this.read(); - while (c != -1 - && (this.isIdent(c) || c == CSS_ESCAPE)) { - if (c == CSS_ESCAPE) - s += this.gatherEscape(); - else - s += c; - c = this.read(); - } - if (c != -1) - this.pushback(); - return s; - }, - - parseIdent: function(c) { - var value = this.gatherIdent(c); - var nextChar = this.peek(); - if (nextChar == "(") { - value += this.read(); - return new jscsspToken(jscsspToken.FUNCTION_TYPE, value); - } - return new jscsspToken(jscsspToken.IDENT_TYPE, value); - }, - - isDigit: function(c) { - return (c >= '0') && (c <= '9'); - }, - - parseComment: function(c) { - var s = c; - while ((c = this.read()) != -1) { - s += c; - if (c == "*") { - c = this.read(); - if (c == -1) - break; - if (c == "/") { - s += c; - break; - } - this.pushback(); - } - } - return new jscsspToken(jscsspToken.COMMENT_TYPE, s); - }, - - parseNumber: function(c) { - var s = c; - var foundDot = false; - while ((c = this.read()) != -1) { - if (c == ".") { - if (foundDot) - break; - else { - s += c; - foundDot = true; - } - } else if (this.isDigit(c)) - s += c; - else - break; - } - - if (c != -1 && this.startsWithIdent(c, this.peek())) { // DIMENSION - var unit = this.gatherIdent(c); - s += unit; - return new jscsspToken(jscsspToken.DIMENSION_TYPE, s, unit); - } - else if (c == "%") { - s += "%"; - return new jscsspToken(jscsspToken.PERCENTAGE_TYPE, s); - } - else if (c != -1) - this.pushback(); - return new jscsspToken(jscsspToken.NUMBER_TYPE, s); - }, - - parseString: function(aStop) { - var s = aStop; - var previousChar = aStop; - var c; - while ((c = this.read()) != -1) { - if (c == aStop && previousChar != CSS_ESCAPE) { - s += c; - break; - } - else if (c == CSS_ESCAPE) { - c = this.peek(); - if (c == -1) - break; - else if (c == "\n" || c == "\r" || c == "\f") { - d = c; - c = this.read(); - // special for Opera that preserves \r\n... - if (d == "\r") { - c = this.peek(); - if (c == "\n") - c = this.read(); - } - } - else { - s += this.gatherEscape(); - c = this.peek(); - } - } - else if (c == "\n" || c == "\r" || c == "\f") { - break; - } - else - s += c; - - previousChar = c; - } - return new jscsspToken(jscsspToken.STRING_TYPE, s); - }, - - isWhiteSpace: function(c) { - var code = c.charCodeAt(0); - return code < 256 && (this.kLexTable[code] & IS_WHITESPACE) != 0; - }, - - eatWhiteSpace: function(c) { - var s = c; - while ((c = this.read()) != -1) { - if (!this.isWhiteSpace(c)) - break; - s += c; - } - if (c != -1) - this.pushback(); - return s; - }, - - parseAtKeyword: function(c) { - return new jscsspToken(jscsspToken.ATRULE_TYPE, this.gatherIdent(c)); - }, - - nextToken: function() { - var c = this.read(); - if (c == -1) - return new jscsspToken(jscsspToken.NULL_TYPE, null); - - if (this.startsWithIdent(c, this.peek())) - return this.parseIdent(c); - - if (c == '@') { - var nextChar = this.read(); - if (nextChar != -1) { - var followingChar = this.peek(); - this.pushback(); - if (this.startsWithIdent(nextChar, followingChar)) - return this.parseAtKeyword(c); - } - } - - if (c == "." || c == "+" || c == "-") { - var nextChar = this.peek(); - if (this.isDigit(nextChar)) - return this.parseNumber(c); - else if (nextChar == "." && c != ".") { - firstChar = this.read(); - var secondChar = this.peek(); - this.pushback(); - if (this.isDigit(secondChar)) - return this.parseNumber(c); - } - } - if (this.isDigit(c)) { - return this.parseNumber(c); - } - - if (c == "'" || c == '"') - return this.parseString(c); - - if (this.isWhiteSpace(c)) { - var s = this.eatWhiteSpace(c); - - return new jscsspToken(jscsspToken.WHITESPACE_TYPE, s); - } - - if (c == "|" || c == "~" || c == "^" || c == "$" || c == "*") { - var nextChar = this.read(); - if (nextChar == "=") { - switch (c) { - case "~" : - return new jscsspToken(jscsspToken.INCLUDES_TYPE, "~="); - case "|" : - return new jscsspToken(jscsspToken.DASHMATCH_TYPE, "|="); - case "^" : - return new jscsspToken(jscsspToken.BEGINSMATCH_TYPE, "^="); - case "$" : - return new jscsspToken(jscsspToken.ENDSMATCH_TYPE, "$="); - case "*" : - return new jscsspToken(jscsspToken.CONTAINSMATCH_TYPE, "*="); - default : - break; - } - } else if (nextChar != -1) - this.pushback(); - } - - if (c == "/" && this.peek() == "*") - return this.parseComment(c); - - return new jscsspToken(jscsspToken.SYMBOL_TYPE, c); - } -}; - -CSSParser.prototype.parseBackgroundImages = function() -{ - var backgrounds = []; - var token = this.getToken(true, true); - while (token.isNotNull()) { - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - backgrounds.push( { type: "image", value: "url(" + urlContent }); - token = this.getToken(true, true); - } - else if (token.isFunction("linear-gradient(") - || token.isFunction("radial-gradient(") - || token.isFunction("repeating-linear-gradient(") - || token.isFunction("repeating-radial-gradient(")) { - this.ungetToken(); - var gradient = this.parseGradient(); - if (gradient) { - backgrounds.push({ - type: gradient.isRadial ? "radial-gradient" : "linear-gradient", - value: gradient - }); - token = this.getToken(true, true); - } - else - return null; - } - else if (token.isIdent("none") - || token.isIdent("inherit") - || token.isIdent("initial")) { - backgrounds.push( { type: token.value }); - token = this.getToken(true, true); - } - else - return null; - - if (token.isSymbol(",")) { - token = this.getToken(true, true); - if (!token.isNotNull()) - return null; - } - } - return backgrounds; -}; - -CSSParser.prototype.parseBackgroundShorthand = function(token, aDecl, aAcceptPriority) -{ - var kHPos = { - "left" : true, - "right" : true - }; - var kVPos = { - "top" : true, - "bottom" : true - }; - var kPos = { - "left" : true, - "right" : true, - "top" : true, - "bottom" : true, - "center" : true - }; - - var bgColor = null; - var bgRepeat = null; - var bgAttachment = null; - var bgImage = null; - var bgPosition = null; - - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!bgColor - && !bgRepeat - && !bgAttachment - && !bgImage - && !bgPosition - && token.isIdent(this.kINHERIT)) { - bgColor = this.kINHERIT; - bgRepeat = this.kINHERIT; - bgAttachment = this.kINHERIT; - bgImage = this.kINHERIT; - bgPosition = this.kINHERIT; - } - - else { - if (!bgAttachment - && (token.isIdent("scroll") || token.isIdent("fixed"))) { - bgAttachment = token.value; - } - - else if (!bgPosition - && ((token.isIdent() && token.value in kPos) - || token.isDimension() - || token.isNumber("0") - || token.isPercentage())) { - bgPosition = token.value; - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || token.isPercentage()) { - bgPosition += " " + token.value; - } else if (token.isIdent() && token.value in kPos) { - if ((bgPosition in kHPos && token.value in kHPos) - || (bgPosition in kVPos && token.value in kVPos)) - return ""; - bgPosition += " " + token.value; - } else { - this.ungetToken(); - bgPosition += " center"; - } - } - - else if (!bgRepeat - && (token.isIdent("repeat") - || token.isIdent("repeat-x") - || token.isIdent("repeat-y") - || token.isIdent("no-repeat"))) { - bgRepeat = token.value; - } - - else if (!bgImage - && (token.isFunction("url(") || token.isIdent("none"))) { - bgImage = token.value; - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var url = this.parseURL(token); // TODO - if (url) - bgImage += url; - else - return ""; - } - } - - else if (!bgImage - && (token.isFunction("linear-gradient(") - || token.isFunction("radial-gradient(") - || token.isFunction("repeating-linear-gradient(") || token.isFunction("repeating-radial-gradient("))) { - this.ungetToken(); - var gradient = this.parseGradient(); - if (gradient) - bgImage = this.serializeGradient(gradient); - else - return ""; - } - - else { - var color = this.parseColor(token); - if (!bgColor && color) - bgColor = color; - else - return ""; - } - - } - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - bgColor = bgColor ? bgColor : "transparent"; - bgImage = bgImage ? bgImage : "none"; - bgRepeat = bgRepeat ? bgRepeat : "repeat"; - bgAttachment = bgAttachment ? bgAttachment : "scroll"; - bgPosition = bgPosition ? bgPosition : "top left"; - - aDecl.push(this._createJscsspDeclarationFromValue("background-color", bgColor)); - aDecl.push(this._createJscsspDeclarationFromValue("background-image", bgImage)); - aDecl.push(this._createJscsspDeclarationFromValue("background-repeat", bgRepeat)); - aDecl.push(this._createJscsspDeclarationFromValue("background-attachment", bgAttachment)); - aDecl.push(this._createJscsspDeclarationFromValue("background-position", bgPosition)); - - return bgColor + " " + bgImage + " " + bgRepeat + " " + bgAttachment + " " + bgPosition; -}; -CSSParser.prototype.parseBorderColorShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - token = this.getToken(true, true); - break; - } - - else { - var color = this.parseColor(token); - if (color) - values.push(color); - else - return ""; - } - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-color", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-color", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-color", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-color", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBorderEdgeOrOutlineShorthand = function(token, aDecl, aAcceptPriority, aProperty) -{ - var bWidth = null; - var bStyle = null; - var bColor = null; - - while (true) { - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!bWidth - && !bStyle - && !bColor - && token.isIdent(this.kINHERIT)) { - bWidth = this.kINHERIT; - bStyle = this.kINHERIT; - bColor = this.kINHERIT; - } - - else if (!bWidth && - (token.isDimension() - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES) - || token.isNumber("0"))) { - bWidth = token.value; - } - - else if (!bStyle && - (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES)) { - bStyle = token.value; - } - - else { - var color = (aProperty == "outline" && token.isIdent("invert")) - ? "invert" : this.parseColor(token); - if (!bColor && color) - bColor = color; - else - return ""; - } - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - bWidth = bWidth ? bWidth : "medium"; - bStyle = bStyle ? bStyle : "none"; - bColor = bColor ? bColor : "-moz-initial"; - - function addPropertyToDecl(aSelf, aDecl, property, w, s, c) { - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-width", w)); - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-style", s)); - aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-color", c)); - } - - if (aProperty == "border") { - addPropertyToDecl(this, aDecl, "border-top", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-right", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-bottom", bWidth, bStyle, bColor); - addPropertyToDecl(this, aDecl, "border-left", bWidth, bStyle, bColor); - } - else - addPropertyToDecl(this, aDecl, aProperty, bWidth, bStyle, bColor); - return bWidth + " " + bStyle + " " + bColor; -}; - -CSSParser.prototype.parseBorderImage = function() -{ - var borderImage = {url: "", offsets: [], widths: [], sizes: []}; - var token = this.getToken(true, true); - if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - borderImage.url = urlContent.substr(0, urlContent.length - 1).trim(); - if ((borderImage.url[0] == '"' && borderImage.url[borderImage.url.length - 1] == '"') - || (borderImage.url[0] == "'" && borderImage.url[borderImage.url.length - 1] == "'")) - borderImage.url = borderImage.url.substr(1, borderImage.url.length - 2); - } - else - return null; - } - else - return null; - - token = this.getToken(true, true); - if (token.isNumber() - || token.isPercentage()) - borderImage.offsets.push(token.value); - else - return null; - var i; - for (i= 0; i < 3; i++) { - token = this.getToken(true, true); - if (token.isNumber() - || token.isPercentage()) - borderImage.offsets.push(token.value); - else - break; - } - if (i == 3) - token = this.getToken(true, true); - - if (token.isSymbol("/")) { - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) - borderImage.widths.push(token.value); - else - return null; - - for (var i = 0; i < 3; i++) { - token = this.getToken(true, true); - if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) - borderImage.widths.push(token.value); - else - break; - } - if (i == 3) - token = this.getToken(true, true); - } - - for (var i = 0; i < 2; i++) { - if (token.isIdent("stretch") - || token.isIdent("repeat") - || token.isIdent("round")) - borderImage.sizes.push(token.value); - else if (!token.isNotNull()) - return borderImage; - else - return null; - token = this.getToken(true, true); - } - if (!token.isNotNull()) - return borderImage; - - return null; -}; - -CSSParser.prototype.parseBorderStyleShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isIdent() && token.value in this.kBORDER_STYLE_NAMES) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-style", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-style", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-style", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-style", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBorderWidthShorthand = function(token, aDecl, aAcceptPriority) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isDimension() - || token.isNumber("0") - || (token.isIdent() && token.value in this.kBORDER_WIDTH_NAMES)) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("border-top-width", top)); - aDecl.push(this._createJscsspDeclarationFromValue("border-right-width", right)); - aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-width", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue("border-left-width", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseBoxShadows = function() -{ - var shadows = []; - var token = this.getToken(true, true); - var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px", spreadRadius = "0px"; - var inset = false; - while (token.isNotNull()) { - if (token.isIdent("none")) { - shadows.push( { none: true } ); - token = this.getToken(true, true); - } - else { - if (token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetX = token.value; - token = this.getToken(true, true); - } - else - return []; - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetY = token.value; - token = this.getToken(true, true); - } - else - return []; - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var blurRadius = token.value; - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var spreadRadius = token.value; - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - if (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent()) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - - if (!inset && token.isIdent('inset')) { - inset = true; - token = this.getToken(true, true); - } - - shadows.push( { none: false, - color: color, - offsetX: offsetX, offsetY: offsetY, - blurRadius: blurRadius, - spreadRadius: spreadRadius, - inset: inset - } ); - - if (token.isSymbol(",")) { - inset = false; - color = ""; - blurRadius = "0px"; - spreadRadius = "0px" - offsetX = "0px"; - offsetY = "0px"; - token = this.getToken(true, true); - } - else if (!token.isNotNull()) - return shadows; - else - return []; - } - } - return shadows; -}; - -CSSParser.prototype.parseCharsetRule = function(aSheet) { - var token = this.getToken(false, false); - if (token.isAtRule("@charset") && token.value == "@charset") { // lowercase check - var s = token.value; - token = this.getToken(false, false); - s += token.value; - if (token.isWhiteSpace(" ")) { - token = this.getToken(false, false); - s += token.value; - if (token.isString()) { - var encoding = token.value; - token = this.getToken(false, false); - s += token.value; - if (token.isSymbol(";")) { - var rule = new jscsspCharsetRule(); - rule.encoding = encoding; - rule.parsedCssText = s; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - else - this.reportError(kCHARSET_RULE_MISSING_SEMICOLON); - } - else - this.reportError(kCHARSET_RULE_CHARSET_IS_STRING); - } - else - this.reportError(kCHARSET_RULE_MISSING_WS); - } - - this.addUnknownAtRule(aSheet, s); - return false; -}; - -CSSParser.prototype.parseColor = function(token) -{ - var color = ""; - if (token.isFunction("rgb(") - || token.isFunction("rgba(")) { - color = token.value; - var isRgba = token.isFunction("rgba(") - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber() && !token.isPercentage()) - return ""; - color += token.value; - - if (isRgba) { - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - } - - token = this.getToken(true, true); - if (!token.isSymbol(")")) - return ""; - color += token.value; - } - - else if (token.isFunction("hsl(") - || token.isFunction("hsla(")) { - color = token.value; - var isHsla = token.isFunction("hsla(") - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isPercentage()) - return ""; - color += token.value; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isPercentage()) - return ""; - color += token.value; - - if (isHsla) { - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return ""; - color += ", "; - - token = this.getToken(true, true); - if (!token.isNumber()) - return ""; - color += token.value; - } - - token = this.getToken(true, true); - if (!token.isSymbol(")")) - return ""; - color += token.value; - } - - else if (token.isIdent() - && (token.value in this.kCOLOR_NAMES)) - color = token.value; - - else if (token.isSymbol("#")) { - token = this.getHexValue(); - if (!token.isHex()) - return ""; - var length = token.value.length; - if (length != 3 && length != 6) - return ""; - if (token.value.match( /[a-fA-F0-9]/g ).length != length) - return ""; - color = "#" + token.value; - } - return color; -}; - -CSSParser.prototype.parseCueShorthand = function(token, declarations, aAcceptPriority) -{ - var before = ""; - var after = ""; - - var values = []; - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isIdent("none")) - values.push(token.value); - - else if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) - values.push("url(" + urlContent); - else - return ""; - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - before = values[0]; - after = before; - break; - case 2: - before = values[0]; - after = values[1]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("cue-before", before)); - aDecl.push(this._createJscsspDeclarationFromValue("cue-after", after)); - return before + " " + after; -}; - -CSSParser.prototype.parseDeclaration = function(aToken, aDecl, aAcceptPriority, aExpandShorthands, aSheet) { - this.preserveState(); - var blocks = []; - if (aToken.isIdent()) { - var descriptor = aToken.value.toLowerCase(); - var token = this.getToken(true, true); - if (token.isSymbol(":")) { - var token = this.getToken(true, true); - - var value = ""; - var declarations = []; - if (aExpandShorthands) - switch (descriptor) { - case "background": - value = this.parseBackgroundShorthand(token, declarations, aAcceptPriority); - break; - case "margin": - case "padding": - value = this.parseMarginOrPaddingShorthand(token, declarations, aAcceptPriority, descriptor); - break; - case "border-color": - value = this.parseBorderColorShorthand(token, declarations, aAcceptPriority); - break; - case "border-style": - value = this.parseBorderStyleShorthand(token, declarations, aAcceptPriority); - break; - case "border-width": - value = this.parseBorderWidthShorthand(token, declarations, aAcceptPriority); - break; - case "border-top": - case "border-right": - case "border-bottom": - case "border-left": - case "border": - case "outline": - value = this.parseBorderEdgeOrOutlineShorthand(token, declarations, aAcceptPriority, descriptor); - break; - case "cue": - value = this.parseCueShorthand(token, declarations, aAcceptPriority); - break; - case "pause": - value = this.parsePauseShorthand(token, declarations, aAcceptPriority); - break; - case "font": - value = this.parseFontShorthand(token, declarations, aAcceptPriority); - break; - case "list-style": - value = this.parseListStyleShorthand(token, declarations, aAcceptPriority); - break; - default: - value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet); - break; - } - else - value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet); - token = this.currentToken(); - if (value) // no error above - { - var priority = false; - if (token.isSymbol("!")) { - token = this.getToken(true, true); - if (token.isIdent("important")) { - priority = true; - token = this.getToken(true, true); - if (token.isSymbol(";") || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - } - else return ""; - } - else return ""; - } - else if (token.isNotNull() && !token.isSymbol(";") && !token.isSymbol("}")) - return ""; - for (var i = 0; i < declarations.length; i++) { - declarations[i].priority = priority; - aDecl.push(declarations[i]); - } - return descriptor + ": " + value + ";"; - } - } - } - else if (aToken.isComment()) { - if (this.mPreserveComments) { - this.forgetState(); - var comment = new jscsspComment(); - comment.parsedCssText = aToken.value; - aDecl.push(comment); - } - return aToken.value; - } - - // we have an error here, let's skip it - this.restoreState(); - var s = aToken.value; - blocks = []; - var token = this.getToken(false, false); - while (token.isNotNull()) { - s += token.value; - if ((token.isSymbol(";") || token.isSymbol("}")) && !blocks.length) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[") - || token.isFunction()) { - blocks.push(token.isFunction() ? "(" : token.value); - } else if (token.isSymbol("}") - || token.isSymbol(")") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - } - } - } - token = this.getToken(false, false); - } - return ""; -}; - -CSSParser.prototype.reportError = function(aMsg) { - this.mError = aMsg; -}; - -CSSParser.prototype.consumeError = function() { - var e = this.mError; - this.mError = null; - return e; -}; - -function CSSParser(aString) -{ - this.mToken = null; - this.mLookAhead = null; - this.mScanner = new CSSScanner(aString); - - this.mPreserveWS = true; - this.mPreserveComments = true; - - this.mPreservedTokens = []; - - this.mError = null; -} - -CSSParser.prototype._init = function() { - this.mToken = null; - this.mLookAhead = null; -}; - -CSSParser.prototype.kINHERIT = "inherit", - -CSSParser.prototype.kBORDER_WIDTH_NAMES = { - "thin": true, - "medium": true, - "thick": true -}; - -CSSParser.prototype.kBORDER_STYLE_NAMES = { - "none": true, - "hidden": true, - "dotted": true, - "dashed": true, - "solid": true, - "double": true, - "groove": true, - "ridge": true, - "inset": true, - "outset": true -}; - -CSSParser.prototype.kCOLOR_NAMES = { - "transparent": true, - - "black": true, - "silver": true, - "gray": true, - "white": true, - "maroon": true, - "red": true, - "purple": true, - "fuchsia": true, - "green": true, - "lime": true, - "olive": true, - "yellow": true, - "navy": true, - "blue": true, - "teal": true, - "aqua": true, - - "aliceblue": true, - "antiquewhite": true, - "aquamarine": true, - "azure": true, - "beige": true, - "bisque": true, - "blanchedalmond": true, - "blueviolet": true, - "brown": true, - "burlywood": true, - "cadetblue": true, - "chartreuse": true, - "chocolate": true, - "coral": true, - "cornflowerblue": true, - "cornsilk": true, - "crimson": true, - "cyan": true, - "darkblue": true, - "darkcyan": true, - "darkgoldenrod": true, - "darkgray": true, - "darkgreen": true, - "darkgrey": true, - "darkkhaki": true, - "darkmagenta": true, - "darkolivegreen": true, - "darkorange": true, - "darkorchid": true, - "darkred": true, - "darksalmon": true, - "darkseagreen": true, - "darkslateblue": true, - "darkslategray": true, - "darkslategrey": true, - "darkturquoise": true, - "darkviolet": true, - "deeppink": true, - "deepskyblue": true, - "dimgray": true, - "dimgrey": true, - "dodgerblue": true, - "firebrick": true, - "floralwhite": true, - "forestgreen": true, - "gainsboro": true, - "ghostwhite": true, - "gold": true, - "goldenrod": true, - "greenyellow": true, - "grey": true, - "honeydew": true, - "hotpink": true, - "indianred": true, - "indigo": true, - "ivory": true, - "khaki": true, - "lavender": true, - "lavenderblush": true, - "lawngreen": true, - "lemonchiffon": true, - "lightblue": true, - "lightcoral": true, - "lightcyan": true, - "lightgoldenrodyellow": true, - "lightgray": true, - "lightgreen": true, - "lightgrey": true, - "lightpink": true, - "lightsalmon": true, - "lightseagreen": true, - "lightskyblue": true, - "lightslategray": true, - "lightslategrey": true, - "lightsteelblue": true, - "lightyellow": true, - "limegreen": true, - "linen": true, - "magenta": true, - "mediumaquamarine": true, - "mediumblue": true, - "mediumorchid": true, - "mediumpurple": true, - "mediumseagreen": true, - "mediumslateblue": true, - "mediumspringgreen": true, - "mediumturquoise": true, - "mediumvioletred": true, - "midnightblue": true, - "mintcream": true, - "mistyrose": true, - "moccasin": true, - "navajowhite": true, - "oldlace": true, - "olivedrab": true, - "orange": true, - "orangered": true, - "orchid": true, - "palegoldenrod": true, - "palegreen": true, - "paleturquoise": true, - "palevioletred": true, - "papayawhip": true, - "peachpuff": true, - "peru": true, - "pink": true, - "plum": true, - "powderblue": true, - "rosybrown": true, - "royalblue": true, - "saddlebrown": true, - "salmon": true, - "sandybrown": true, - "seagreen": true, - "seashell": true, - "sienna": true, - "skyblue": true, - "slateblue": true, - "slategray": true, - "slategrey": true, - "snow": true, - "springgreen": true, - "steelblue": true, - "tan": true, - "thistle": true, - "tomato": true, - "turquoise": true, - "violet": true, - "wheat": true, - "whitesmoke": true, - "yellowgreen": true, - - "activeborder": true, - "activecaption": true, - "appworkspace": true, - "background": true, - "buttonface": true, - "buttonhighlight": true, - "buttonshadow": true, - "buttontext": true, - "captiontext": true, - "graytext": true, - "highlight": true, - "highlighttext": true, - "inactiveborder": true, - "inactivecaption": true, - "inactivecaptiontext": true, - "infobackground": true, - "infotext": true, - "menu": true, - "menutext": true, - "scrollbar": true, - "threeddarkshadow": true, - "threedface": true, - "threedhighlight": true, - "threedlightshadow": true, - "threedshadow": true, - "window": true, - "windowframe": true, - "windowtext": true -}; - -CSSParser.prototype.kLIST_STYLE_TYPE_NAMES = { - "decimal": true, - "decimal-leading-zero": true, - "lower-roman": true, - "upper-roman": true, - "georgian": true, - "armenian": true, - "lower-latin": true, - "lower-alpha": true, - "upper-latin": true, - "upper-alpha": true, - "lower-greek": true, - - "disc": true, - "circle": true, - "square": true, - "none": true, - - /* CSS 3 */ - "box": true, - "check": true, - "diamond": true, - "hyphen": true, - - "lower-armenian": true, - "cjk-ideographic": true, - "ethiopic-numeric": true, - "hebrew": true, - "japanese-formal": true, - "japanese-informal": true, - "simp-chinese-formal": true, - "simp-chinese-informal": true, - "syriac": true, - "tamil": true, - "trad-chinese-formal": true, - "trad-chinese-informal": true, - "upper-armenian": true, - "arabic-indic": true, - "binary": true, - "bengali": true, - "cambodian": true, - "khmer": true, - "devanagari": true, - "gujarati": true, - "gurmukhi": true, - "kannada": true, - "lower-hexadecimal": true, - "lao": true, - "malayalam": true, - "mongolian": true, - "myanmar": true, - "octal": true, - "oriya": true, - "persian": true, - "urdu": true, - "telugu": true, - "tibetan": true, - "upper-hexadecimal": true, - "afar": true, - "ethiopic-halehame-aa-et": true, - "ethiopic-halehame-am-et": true, - "amharic-abegede": true, - "ehiopic-abegede-am-et": true, - "cjk-earthly-branch": true, - "cjk-heavenly-stem": true, - "ethiopic": true, - "ethiopic-abegede": true, - "ethiopic-abegede-gez": true, - "hangul-consonant": true, - "hangul": true, - "hiragana-iroha": true, - "hiragana": true, - "katakana-iroha": true, - "katakana": true, - "lower-norwegian": true, - "oromo": true, - "ethiopic-halehame-om-et": true, - "sidama": true, - "ethiopic-halehame-sid-et": true, - "somali": true, - "ethiopic-halehame-so-et": true, - "tigre": true, - "ethiopic-halehame-tig": true, - "tigrinya-er-abegede": true, - "ethiopic-abegede-ti-er": true, - "tigrinya-et": true, - "ethiopic-halehame-ti-et": true, - "upper-greek": true, - "asterisks": true, - "footnotes": true, - "circled-decimal": true, - "circled-lower-latin": true, - "circled-upper-latin": true, - "dotted-decimal": true, - "double-circled-decimal": true, - "filled-circled-decimal": true, - "parenthesised-decimal": true, - "parenthesised-lower-latin": true -}; - -CSSParser.prototype.parseFontFaceRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var descriptors = []; - this.preserveState(); - var token = this.getToken(true, true); - if (token.isNotNull()) { - // expecting block start - if (token.isSymbol("{")) { - s += " " + token.value; - var token = this.getToken(true, false); - while (true) { - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, descriptors, false, false, aSheet); - s += ((d && descriptors.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspFontFaceRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.descriptors = descriptors; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseFontShorthand = function(token, aDecl, aAcceptPriority) -{ - var kStyle = {"italic": true, "oblique": true }; - var kVariant = {"small-caps": true }; - var kWeight = { "bold": true, "bolder": true, "lighter": true, - "100": true, "200": true, "300": true, "400": true, - "500": true, "600": true, "700": true, "800": true, - "900": true }; - var kSize = { "xx-small": true, "x-small": true, "small": true, "medium": true, - "large": true, "x-large": true, "xx-large": true, - "larger": true, "smaller": true }; - var kValues = { "caption": true, "icon": true, "menu": true, "message-box": true, "small-caption": true, "status-bar": true }; - var kFamily = { "serif": true, "sans-serif": true, "cursive": true, "fantasy": true, "monospace": true }; - - var fStyle = null; - var fVariant = null; - var fWeight = null; - var fSize = null; - var fLineHeight = null; - var fFamily = ""; - var fSystem = null; - var fFamilyValues = []; - - var normalCount = 0; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!fStyle && !fVariant && !fWeight - && !fSize && !fLineHeight && !fFamily - && !fSystem - && token.isIdent(this.kINHERIT)) { - fStyle = this.kINHERIT; - fVariant = this.kINHERIT; - fWeight = this.kINHERIT; - fSize = this.kINHERIT; - fLineHeight = this.kINHERIT; - fFamily = this.kINHERIT; - fSystem = this.kINHERIT; - } - - else { - if (!fSystem && (token.isIdent() && token.value in kValues)) { - fSystem = token.value; - break; - } - - else { - if (!fStyle - && token.isIdent() - && (token.value in kStyle)) { - fStyle = token.value; - } - - else if (!fVariant - && token.isIdent() - && (token.value in kVariant)) { - fVariant = token.value; - } - - else if (!fWeight - && (token.isIdent() || token.isNumber()) - && (token.value in kWeight)) { - fWeight = token.value; - } - - else if (!fSize - && ((token.isIdent() && (token.value in kSize)) - || token.isDimension() - || token.isPercentage())) { - fSize = token.value; - token = this.getToken(false, false); - if (token.isSymbol("/")) { - token = this.getToken(false, false); - if (!fLineHeight && - (token.isDimension() || token.isNumber() || token.isPercentage())) { - fLineHeight = token.value; - } - else - return ""; - } - else if (!token.isWhiteSpace()) - continue; - } - - else if (token.isIdent("normal")) { - normalCount++; - if (normalCount > 3) - return ""; - } - - else if (!fFamily && // *MUST* be last to be tested here - (token.isString() - || token.isIdent())) { - var lastWasComma = false; - while (true) { - if (!token.isNotNull()) - break; - else if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - this.ungetToken(); - break; - } - else if (token.isIdent() && token.value in kFamily) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = token.value; - fFamilyValues.push(value); - fFamily += token.value; - break; - } - else if (token.isString() || token.isIdent()) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = token.value; - fFamilyValues.push(value); - fFamily += token.value; - lastWasComma = false; - } - else if (!lastWasComma && token.isSymbol(",")) { - fFamily += ", "; - lastWasComma = true; - } - else - return ""; - token = this.getToken(true, true); - } - } - - else { - return ""; - } - } - - } - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - if (fSystem) { - aDecl.push(this._createJscsspDeclarationFromValue("font", fSystem)); - return fSystem; - } - fStyle = fStyle ? fStyle : "normal"; - fVariant = fVariant ? fVariant : "normal"; - fWeight = fWeight ? fWeight : "normal"; - fSize = fSize ? fSize : "medium"; - fLineHeight = fLineHeight ? fLineHeight : "normal"; - fFamily = fFamily ? fFamily : "-moz-initial"; - - aDecl.push(this._createJscsspDeclarationFromValue("font-style", fStyle)); - aDecl.push(this._createJscsspDeclarationFromValue("font-variant", fVariant)); - aDecl.push(this._createJscsspDeclarationFromValue("font-weight", fWeight)); - aDecl.push(this._createJscsspDeclarationFromValue("font-size", fSize)); - aDecl.push(this._createJscsspDeclarationFromValue("line-height", fLineHeight)); - aDecl.push(this._createJscsspDeclarationFromValuesArray("font-family", fFamilyValues, fFamily)); - return fStyle + " " + fVariant + " " + fWeight + " " + fSize + "/" + fLineHeight + " " + fFamily; -}; - -CSSParser.prototype.parseFunctionArgument = function(token) -{ - var value = ""; - if (token.isString()) - { - value += token.value; - token = this.getToken(true, true); - } - else { - var parenthesis = 1; - while (true) - { - if (!token.isNotNull()) - return ""; - if (token.isFunction() || token.isSymbol("(")) - parenthesis++; - if (token.isSymbol(")")) { - parenthesis--; - if (!parenthesis) - break; - } - value += token.value; - token = this.getToken(false, false); - } - } - - if (token.isSymbol(")")) - return value + ")"; - return ""; -}; - -CSSParser.prototype.parseColorStop = function(token) -{ - var color = this.parseColor(token); - var position = ""; - if (!color) - return null; - token = this.getToken(true, true); - if (token.isLength()) { - position = token.value; - token = this.getToken(true, true); - } - return { color: color, position: position } -}; - -CSSParser.prototype.parseGradient = function () -{ - var kHPos = {"left": true, "right": true }; - var kVPos = {"top": true, "bottom": true }; - var kPos = {"left": true, "right": true, "top": true, "bottom": true, "center": true}; - - var isRadial = false; - var gradient = { isRepeating: false }; - var token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isFunction("linear-gradient(") || - token.isFunction("radial-gradient(") || - token.isFunction("repeating-linear-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - if (token.isFunction("radial-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - gradient.isRadial = true; - } - if (token.isFunction("repeating-linear-gradient(") || - token.isFunction("repeating-radial-gradient(")) { - gradient.isRepeating = true; - } - - - token = this.getToken(true, true); - var foundPosition = false; - var haveAngle = false; - - /********** LINEAR **********/ - if (token.isAngle()) { - gradient.angle = token.value; - haveAngle = true; - token = this.getToken(true, true); - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - - else if (token.isIdent("to")) { - foundPosition = true; - token = this.getToken(true, true); - if (token.isIdent("top") - || token.isIdent("bottom") - || token.isIdent("left") - || token.isIdent("right")) { - gradient.position = token.value; - token = this.getToken(true, true); - if (((gradient.position == "top" || gradient.position == "bottom") && (token.isIdent("left") || token.isIdent("right"))) - || ((gradient.position == "left" || gradient.position == "right") && (token.isIdent("top") || token.isIdent("bottom")))) { - gradient.position += " " + token.value; - token = this.getToken(true, true); - } - } - else - return null; - - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - - /********** RADIAL **********/ - else if (gradient.isRadial) { - gradient.shape = ""; - gradient.extent = ""; - gradient.positions = []; - gradient.at = ""; - - while (!token.isIdent("at") && !token.isSymbol(",")) { - if (!gradient.shape - && (token.isIdent("circle") || token.isIdent("ellipse"))) { - gradient.shape = token.value; - token = this.getToken(true, true); - } - else if (!gradient.extent - && (token.isIdent("closest-corner") - || token.isIdent("closes-side") - || token.isIdent("farthest-corner") - || token.isIdent("farthest-corner"))) { - gradient.extent = token.value; - token = this.getToken(true, true); - } - else if (gradient.positions.length < 2 && token.isLength()){ - gradient.positions.push(token.value); - token = this.getToken(true, true); - } - else - break; - } - - // verify if the shape is null of well defined - if ((gradient.positions.length == 1 && !gradient.extent && (gradient.shape == "circle" || !gradient.shape)) - || (gradient.positions.length == 2 && !gradient.extent && (gradient.shape == "ellipse" || !gradient.shape)) - || (!gradient.positions.length && gradient.extent) - || (!gradient.positions.length && !gradient.extent)) { - // shape ok - } - else { - return null; - } - - if (token.isIdent("at")) { - token = this.getToken(true, true); - if (((token.isIdent() && token.value in kPos) - || token.isDimension() - || token.isNumber("0") - || token.isPercentage())) { - gradient.at = token.value; - token = this.getToken(true, true); - if (token.isDimension() || token.isNumber("0") || token.isPercentage()) { - gradient.at += " " + token.value; - } - else if (token.isIdent() && token.value in kPos) { - if ((gradient.at in kHPos && token.value in kHPos) || - (gradient.at in kVPos && token.value in kVPos)) - return ""; - gradient.at += " " + token.value; - } - else { - this.ungetToken(); - gradient.at += " center"; - } - } - else - return null; - - token = this.getToken(true, true); - } - - if (gradient.shape || gradient.extent || gradient.positions.length || gradient.at) { - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - } - } - - // now color stops... - var stop1 = this.parseColorStop(token); - if (!stop1) - return null; - token = this.currentToken(); - if (!token.isSymbol(",")) - return null; - token = this.getToken(true, true); - var stop2 = this.parseColorStop(token); - if (!stop2) - return null; - token = this.currentToken(); - if (token.isSymbol(",")) { - token = this.getToken(true, true); - } - // ok we have at least two color stops - gradient.stops = [stop1, stop2]; - while (!token.isSymbol(")")) { - var colorstop = this.parseColorStop(token); - if (!colorstop) - return null; - token = this.currentToken(); - if (!token.isSymbol(")") && !token.isSymbol(",")) - return null; - if (token.isSymbol(",")) - token = this.getToken(true, true); - gradient.stops.push(colorstop); - } - return gradient; - } - } - return null; -}; - -CSSParser.prototype.serializeGradient = function(gradient) -{ - var s = gradient.isRadial - ? (gradient.isRepeating ? "repeating-radial-gradient(" : "radial-gradient(" ) - : (gradient.isRepeating ? "repeating-linear-gradient(" : "linear-gradient(" ); - if (gradient.angle || gradient.position) - s += (gradient.angle ? gradient.angle: "") + - (gradient.position ? "to " + gradient.position : "") + - ", "; - - if (gradient.isRadial) - s += (gradient.shape ? gradient.shape + " " : "") + - (gradient.extent ? gradient.extent + " " : "") + - (gradient.positions.length ? gradient.positions.join(" ") + " " : "") + - (gradient.at ? "at " + gradient.at + " " : "") + - (gradient.shape || gradient.extent || gradient.positions.length || gradient.at ? ", " : ""); - - for (var i = 0; i < gradient.stops.length; i++) { - var colorstop = gradient.stops[i]; - s += colorstop.color + (colorstop.position ? " " + colorstop.position : ""); - if (i != gradient.stops.length -1) - s += ", "; - } - s += ")"; - return s; -}; - - -CSSParser.prototype.parseImportRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - this.preserveState(); - var token = this.getToken(true, true); - var media = []; - var href = ""; - if (token.isString()) { - href = token.value; - s += " " + href; - } - else if (token.isFunction("url(")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - href = "url(" + urlContent; - s += " " + href; - } - } - else - this.reportError(kIMPORT_RULE_MISSING_URL); - - if (href) { - token = this.getToken(true, true); - while (token.isIdent()) { - s += " " + token.value; - media.push(token.value); - token = this.getToken(true, true); - if (!token) - break; - if (token.isSymbol(",")) { - s += ","; - } else if (token.isSymbol(";")) { - break; - } else - break; - token = this.getToken(true, true); - } - - if (!media.length) { - media.push("all"); - } - - if (token.isSymbol(";")) { - s += ";" - this.forgetState(); - var rule = new jscsspImportRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.href = href; - rule.media = media; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - } - - this.restoreState(); - this.addUnknownAtRule(aSheet, "@import"); - return false; -}; - -CSSParser.prototype.parseKeyframesRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var keyframesRule = new jscsspKeyframesRule(); - keyframesRule.currentLine = currentLine; - this.preserveState(); - var token = this.getToken(true, true); - var foundName = false; - while (token.isNotNull()) { - if (token.isIdent()) { - // should be the keyframes' name - foundName = true; - s += " " + token.value; - keyframesRule.name = token.value; - token = this.getToken(true, true); - if (token.isSymbol("{")) - this.ungetToken(); - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - else if (token.isSymbol("{")) { - if (!foundName) { - token.type = jscsspToken.NULL_TYPE; - // not a valid keyframes at-rule - } - break; - } - else { - token.type = jscsspToken.NULL_TYPE; - // not a valid keyframes at-rule - break; - } - token = this.getToken(true, true); - } - - if (token.isSymbol("{") && keyframesRule.name) { - // ok let's parse keyframe rules now... - s += " { "; - token = this.getToken(true, false); - while (token.isNotNull()) { - if (token.isComment() && this.mPreserveComments) { - s += " " + token.value; - var comment = new jscsspComment(); - comment.parsedCssText = token.value; - keyframesRule.cssRules.push(comment); - } else if (token.isSymbol("}")) { - valid = true; - break; - } else { - var r = this.parseKeyframeRule(token, keyframesRule, true); - if (r) - s += r; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - keyframesRule.currentLine = currentLine; - keyframesRule.parsedCssText = s; - aSheet.cssRules.push(keyframesRule); - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseKeyframeRule = function(aToken, aOwner) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - this.preserveState(); - var token = aToken; - - // find the keyframe keys - var key = ""; - while (token.isNotNull()) { - if (token.isIdent() || token.isPercentage()) { - if (token.isIdent() - && !token.isIdent("from") - && !token.isIdent("to")) { - key = ""; - break; - } - key += token.value; - token = this.getToken(true, true); - if (token.isSymbol("{")) { - this.ungetToken(); - break; - } - else - if (token.isSymbol(",")) { - key += ", "; - } - else { - key = ""; - break; - } - } - else { - key = ""; - break; - } - token = this.getToken(true, true); - } - - var valid = false; - var declarations = []; - if (key) { - var s = key; - token = this.getToken(true, true); - if (token.isSymbol("{")) { - s += " { "; - token = this.getToken(true, false); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aOwner); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - else { - // key is invalid so the whole rule is invalid with it - } - - if (valid) { - var rule = new jscsspKeyframeRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.keyText = key; - rule.parentRule = aOwner; - aOwner.cssRules.push(rule); - return s; - } - this.restoreState(); - s = this.currentToken().value; - this.addUnknownAtRule(aOwner, s); - return ""; -}; - -CSSParser.prototype.parseListStyleShorthand = function(token, aDecl, aAcceptPriority) -{ - var kPosition = { "inside": true, "outside": true }; - - var lType = null; - var lPosition = null; - var lImage = null; - - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!lType && !lPosition && ! lImage - && token.isIdent(this.kINHERIT)) { - lType = this.kINHERIT; - lPosition = this.kINHERIT; - lImage = this.kINHERIT; - } - - else if (!lType && - (token.isIdent() && token.value in this.kLIST_STYLE_TYPE_NAMES)) { - lType = token.value; - } - - else if (!lPosition && - (token.isIdent() && token.value in kPosition)) { - lPosition = token.value; - } - - else if (!lImage && token.isFunction("url")) { - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - lImage = "url(" + urlContent; - } - else - return ""; - } - else if (!token.isIdent("none")) - return ""; - - token = this.getToken(true, true); - } - - // create the declarations - this.forgetState(); - lType = lType ? lType : "none"; - lImage = lImage ? lImage : "none"; - lPosition = lPosition ? lPosition : "outside"; - - aDecl.push(this._createJscsspDeclarationFromValue("list-style-type", lType)); - aDecl.push(this._createJscsspDeclarationFromValue("list-style-position", lPosition)); - aDecl.push(this._createJscsspDeclarationFromValue("list-style-image", lImage)); - return lType + " " + lPosition + " " + lImage; -}; - -CSSParser.prototype.parse = function(aString, aTryToPreserveWhitespaces, aTryToPreserveComments) { - if (!aString) - return null; // early way out if we can - - this.mPreserveWS = aTryToPreserveWhitespaces; - this.mPreserveComments = aTryToPreserveComments; - this.mPreservedTokens = []; - this.mScanner.init(aString); - var sheet = new jscsspStylesheet(); - - // @charset can only appear at first char of the stylesheet - var token = this.getToken(false, false); - if (!token.isNotNull()) - return sheet; - if (token.isAtRule("@charset")) { - this.ungetToken(); - this.parseCharsetRule(sheet); - token = this.getToken(false, false); - } - - var foundStyleRules = false; - var foundImportRules = false; - var foundNameSpaceRules = false; - while (true) { - if (!token.isNotNull()) - break; - if (token.isWhiteSpace()) - { - if (aTryToPreserveWhitespaces) - this.addWhitespace(sheet, token.value); - } - - else if (token.isComment()) - { - if (this.mPreserveComments) - this.addComment(sheet, token.value); - } - - else if (token.isAtRule()) { - if (token.isAtRule("@variables")) { - if (!foundImportRules && !foundStyleRules) - this.parseVariablesRule(token, sheet); - else { - this.reportError(kVARIABLES_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@import")) { - // @import rules MUST occur before all style and namespace - // rules - if (!foundStyleRules && !foundNameSpaceRules) - foundImportRules = this.parseImportRule(token, sheet); - else { - this.reportError(kIMPORT_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@namespace")) { - // @namespace rules MUST occur before all style rule and - // after all @import rules - if (!foundStyleRules) - foundNameSpaceRules = this.parseNamespaceRule(token, sheet); - else { - this.reportError(kNAMESPACE_RULE_POSITION); - this.addUnknownAtRule(sheet, token.value); - } - } - else if (token.isAtRule("@font-face")) { - if (this.parseFontFaceRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@page")) { - if (this.parsePageRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@media")) { - if (this.parseMediaRule(token, sheet)) - foundStyleRules = true; - else - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@keyframes")) { - if (!this.parseKeyframesRule(token, sheet)) - this.addUnknownAtRule(sheet, token.value); - } - else if (token.isAtRule("@charset")) { - this.reportError(kCHARSET_RULE_CHARSET_SOF); - this.addUnknownAtRule(sheet, token.value); - } - else { - this.reportError(kUNKNOWN_AT_RULE); - this.addUnknownAtRule(sheet, token.value); - } - } - - else // plain style rules - { - var ruleText = this.parseStyleRule(token, sheet, false); - if (ruleText) - foundStyleRules = true; - } - token = this.getToken(false); - } - - return sheet; -}; -CSSParser.prototype.parseMarginOrPaddingShorthand = function(token, aDecl, aAcceptPriority, aProperty) -{ - var top = null; - var bottom = null; - var left = null; - var right = null; - - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - token = this.getToken(true, true); - break; - } - - else if (token.isDimension() - || token.isNumber("0") - || token.isPercentage() - || token.isIdent("auto")) { - values.push(token.value); - } - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - top = values[0]; - bottom = top; - left = top; - right = top; - break; - case 2: - top = values[0]; - bottom = top; - left = values[1]; - right = left; - break; - case 3: - top = values[0]; - left = values[1]; - right = left; - bottom = values[2]; - break; - case 4: - top = values[0]; - right = values[1]; - bottom = values[2]; - left = values[3]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-top", top)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-right", right)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-bottom", bottom)); - aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-left", left)); - return top + " " + right + " " + bottom + " " + left; -}; - -CSSParser.prototype.parseMediaQuery = function() -{ - var kCONSTRAINTS = { - "width": true, - "min-width": true, - "max-width": true, - "height": true, - "min-height": true, - "max-height": true, - "device-width": true, - "min-device-width": true, - "max-device-width": true, - "device-height": true, - "min-device-height": true, - "max-device-height": true, - "orientation": true, - "aspect-ratio": true, - "min-aspect-ratio": true, - "max-aspect-ratio": true, - "device-aspect-ratio": true, - "min-device-aspect-ratio": true, - "max-device-aspect-ratio": true, - "color": true, - "min-color": true, - "max-color": true, - "color-index": true, - "min-color-index": true, - "max-color-index": true, - "monochrome": true, - "min-monochrome": true, - "max-monochrome": true, - "resolution": true, - "min-resolution": true, - "max-resolution": true, - "scan": true, - "grid": true - }; - - var m = {cssText: "", amplifier: "", medium: "", constraints: []}; - var token = this.getToken(true, true); - - if (token.isIdent("all") || - token.isIdent("aural") || - token.isIdent("braille") || - token.isIdent("handheld") || - token.isIdent("print") || - token.isIdent("projection") || - token.isIdent("screen") || - token.isIdent("tty") || - token.isIdent("tv")) { - m.medium = token.value; - m.cssText += token.value; - token = this.getToken(true, true); - } - else if (token.isIdent("not") || token.isIdent("only")) { - m.amplifier = token.value.toLowerCase(); - m.cssText += token.value.toLowerCase(); - token = this.getToken(true, true); - if (token.isIdent("all") || - token.isIdent("aural") || - token.isIdent("braille") || - token.isIdent("handheld") || - token.isIdent("print") || - token.isIdent("projection") || - token.isIdent("screen") || - token.isIdent("tty") || - token.isIdent("tv")) { - m.cssText += " " + token.value; - m.medium = token.value; - token = this.getToken(true, true); - } - else - return null; - } - - if (m.medium) { - if (!token.isNotNull()) - return m; - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else if (!token.isSymbol("{")) - return null; - } - - while (token.isSymbol("(")) { - token = this.getToken(true, true); - if (token.isIdent() && (token.value in kCONSTRAINTS)) { - var constraint = token.value; - token = this.getToken(true, true); - if (token.isSymbol(":")) { - token = this.getToken(true, true); - var values = []; - while (!token.isSymbol(")")) { - values.push(token.value); - token = this.getToken(true, true); - } - if (token.isSymbol(")")) { - m.constraints.push({constraint: constraint, value: values}); - m.cssText += " (" + constraint + ": " + values.join(" ") + ")"; - token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else if (!token.isSymbol("{")) - return null; - } - else - return m; - } - else - return null; - } - else if (token.isSymbol(")")) { - m.constraints.push({constraint: constraint, value: null}); - m.cssText += " (" + constraint + ")"; - token = this.getToken(true, true); - if (token.isNotNull()) { - if (token.isIdent("and")) { - m.cssText += " and"; - token = this.getToken(true, true); - } - else - return null; - } - else - return m; - } - else - return null; - } - else - return null; - } - return m; -}; - -CSSParser.prototype.parseMediaRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var mediaRule = new jscsspMediaRule(); - mediaRule.currentLine = currentLine; - this.preserveState(); - var token = this.getToken(true, true); - var foundMedia = false; - while (token.isNotNull()) { - this.ungetToken(); - var mediaQuery = this.parseMediaQuery(); - token = this.currentToken(); - if (mediaQuery) { - foundMedia = true; - s += " " + mediaQuery.cssText; - mediaRule.media.push(mediaQuery.cssText); - if (token.isSymbol(",")) { - s += ","; - } else { - if (token.isSymbol("{")) - break; - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - } - else if (token.isSymbol("{")) - break; - else if (foundMedia) { - token.type = jscsspToken.NULL_TYPE; - // not a media list - break; - } - - token = this.getToken(true, true); - } - if (token.isSymbol("{") && mediaRule.media.length) { - // ok let's parse style rules now... - s += " { "; - token = this.getToken(true, false); - while (token.isNotNull()) { - if (token.isComment()) { - if (this.mPreserveComments) { - s += " " + token.value; - var comment = new jscsspComment(); - comment.parsedCssText = token.value; - mediaRule.cssRules.push(comment); - } - } else if (token.isSymbol("}")) { - valid = true; - break; - } else { - var r = this.parseStyleRule(token, mediaRule, true); - if (r) - s += r; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - mediaRule.parsedCssText = s; - aSheet.cssRules.push(mediaRule); - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parseNamespaceRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - this.preserveState(); - var token = this.getToken(true, true); - if (token.isNotNull()) { - var prefix = ""; - var url = ""; - if (token.isIdent()) { - prefix = token.value; - s += " " + prefix; - token = this.getToken(true, true); - } - if (token) { - var foundURL = false; - if (token.isString()) { - foundURL = true; - url = token.value; - s += " " + url; - } else if (token.isFunction("url(")) { - // get a url here... - token = this.getToken(true, true); - var urlContent = this.parseURL(token); - if (urlContent) { - url += "url(" + urlContent; - foundURL = true; - s += " " + urlContent; - } - } - } - if (foundURL) { - token = this.getToken(true, true); - if (token.isSymbol(";")) { - s += ";"; - this.forgetState(); - var rule = new jscsspNamespaceRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.prefix = prefix; - rule.url = url; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); - return true; - } - } - - } - this.restoreState(); - this.addUnknownAtRule(aSheet, "@namespace"); - return false; -}; - -CSSParser.prototype.parsePageRule = function(aToken, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = aToken.value; - var valid = false; - var declarations = []; - this.preserveState(); - var token = this.getToken(true, true); - var pageSelector = ""; - if (token.isSymbol(":") || token.isIdent()) { - if (token.isSymbol(":")) { - pageSelector = ":"; - token = this.getToken(false, false); - } - if (token.isIdent()) { - pageSelector += token.value; - s += " " + pageSelector; - token = this.getToken(true, true); - } - } - if (token.isNotNull()) { - // expecting block start - if (token.isSymbol("{")) { - s += " " + token.value; - var token = this.getToken(true, false); - while (true) { - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aSheet); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspPageRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.pageSelector = pageSelector; - rule.declarations = declarations; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -CSSParser.prototype.parsePauseShorthand = function(token, declarations, aAcceptPriority) -{ - var before = ""; - var after = ""; - - var values = []; - var values = []; - while (true) { - - if (!token.isNotNull()) - break; - - if (token.isSymbol(";") - || (aAcceptPriority && token.isSymbol("!")) - || token.isSymbol("}")) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - else if (!values.length && token.isIdent(this.kINHERIT)) { - values.push(token.value); - } - - else if (token.isDimensionOfUnit("ms") - || token.isDimensionOfUnit("s") - || token.isPercentage() - || token.isNumber("0")) - values.push(token.value); - else - return ""; - - token = this.getToken(true, true); - } - - var count = values.length; - switch (count) { - case 1: - before = values[0]; - after = before; - break; - case 2: - before = values[0]; - after = values[1]; - break; - default: - return ""; - } - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValue("pause-before", before)); - aDecl.push(this._createJscsspDeclarationFromValue("pause-after", after)); - return before + " " + after; -}; - -CSSParser.prototype.parseDefaultPropertyValue = function(token, aDecl, aAcceptPriority, descriptor, aSheet) { - var valueText = ""; - var blocks = []; - var foundPriority = false; - var values = []; - while (token.isNotNull()) { - - if ((token.isSymbol(";") - || token.isSymbol("}") - || token.isSymbol("!")) - && !blocks.length) { - if (token.isSymbol("}")) - this.ungetToken(); - break; - } - - if (token.isIdent(this.kINHERIT)) { - if (values.length) { - return ""; - } - else { - valueText = this.kINHERIT; - var value = new jscsspVariable(kJscsspINHERIT_VALUE, aSheet); - values.push(value); - token = this.getToken(true, true); - break; - } - } - else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[")) { - blocks.push(token.value); - } - else if (token.isSymbol("}") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - } - } - } - // XXX must find a better way to store individual values - // probably a |values: []| field holding dimensions, percentages - // functions, idents, numbers and symbols, in that order. - if (token.isFunction()) { - if (token.isFunction("var(")) { - token = this.getToken(true, true); - if (token.isIdent()) { - var name = token.value; - token = this.getToken(true, true); - if (token.isSymbol(")")) { - var value = new jscsspVariable(kJscsspVARIABLE_VALUE, aSheet); - valueText += "var(" + name + ")"; - value.name = name; - values.push(value); - } - else - return ""; - } - else - return ""; - } - else { - var fn = token.value; - token = this.getToken(false, true); - var arg = this.parseFunctionArgument(token); - if (arg) { - valueText += fn + arg; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = fn + arg; - values.push(value); - } - else - return ""; - } - } - else if (token.isSymbol("#")) { - var color = this.parseColor(token); - if (color) { - valueText += color; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = color; - values.push(value); - } - else - return ""; - } - else if (!token.isWhiteSpace() && !token.isSymbol(",")) { - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet); - value.value = token.value; - values.push(value); - valueText += token.value; - } - else - valueText += token.value; - token = this.getToken(false, true); - } - if (values.length && valueText) { - this.forgetState(); - aDecl.push(this._createJscsspDeclarationFromValuesArray(descriptor, values, valueText)); - return valueText; - } - return ""; -}; - -CSSParser.prototype.parseSelector = function(aToken, aParseSelectorOnly) { - var s = ""; - var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3 - var isFirstInChain = true; - var token = aToken; - var valid = false; - var combinatorFound = false; - while (true) { - if (!token.isNotNull()) { - if (aParseSelectorOnly) - return {selector: s, specificity: specificity }; - return ""; - } - - if (!aParseSelectorOnly && token.isSymbol("{")) { - // end of selector - valid = !combinatorFound; - // don't unget if invalid since addUnknownRule is going to restore state anyway - if (valid) - this.ungetToken(); - break; - } - - if (token.isSymbol(",")) { // group of selectors - s += token.value; - isFirstInChain = true; - combinatorFound = false; - token = this.getToken(false, true); - continue; - } - // now combinators and grouping... - else if (!combinatorFound - && (token.isWhiteSpace() - || token.isSymbol(">") - || token.isSymbol("+") - || token.isSymbol("~"))) { - if (token.isWhiteSpace()) { - s += " "; - var nextToken = this.lookAhead(true, true); - if (!nextToken.isNotNull()) { - if (aParseSelectorOnly) - return {selector: s, specificity: specificity }; - return ""; - } - if (nextToken.isSymbol(">") - || nextToken.isSymbol("+") - || nextToken.isSymbol("~")) { - token = this.getToken(true, true); - s += token.value + " "; - combinatorFound = true; - } - } - else { - s += token.value; - combinatorFound = true; - } - isFirstInChain = true; - token = this.getToken(true, true); - continue; - } - else { - var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, true); - if (!simpleSelector) - break; // error - s += simpleSelector.selector; - specificity.b += simpleSelector.specificity.b; - specificity.c += simpleSelector.specificity.c; - specificity.d += simpleSelector.specificity.d; - isFirstInChain = false; - combinatorFound = false; - } - - token = this.getToken(false, true); - } - - if (valid) { - return {selector: s, specificity: specificity }; - } - return ""; -}; - -CSSParser.prototype.isPseudoElement = function(aIdent) -{ - switch (aIdent) { - case "first-letter": - case "first-line": - case "before": - case "after": - case "marker": - return true; - break; - default: - break; - } - return false; -}; - -CSSParser.prototype.parseSimpleSelector = function(token, isFirstInChain, canNegate) -{ - var s = ""; - var specificity = {a: 0, b: 0, c: 0, d: 0}; // CSS 2.1 section 6.4.3 - - if (isFirstInChain - && (token.isSymbol("*") || token.isSymbol("|") || token.isIdent())) { - // type or universal selector - if (token.isSymbol("*") || token.isIdent()) { - // we don't know yet if it's a prefix or a universal - // selector - s += token.value; - var isIdent = token.isIdent(); - token = this.getToken(false, true); - if (token.isSymbol("|")) { - // it's a prefix - s += token.value; - token = this.getToken(false, true); - if (token.isIdent() || token.isSymbol("*")) { - // ok we now have a type element or universal - // selector - s += token.value; - if (token.isIdent()) - specificity.d++; - } else - // oops that's an error... - return null; - } else { - this.ungetToken(); - if (isIdent) - specificity.d++; - } - } else if (token.isSymbol("|")) { - s += token.value; - token = this.getToken(false, true); - if (token.isIdent() || token.isSymbol("*")) { - s += token.value; - if (token.isIdent()) - specificity.d++; - } else - // oops that's an error - return null; - } - } - - else if (token.isSymbol(".") || token.isSymbol("#")) { - var isClass = token.isSymbol("."); - s += token.value; - token = this.getToken(false, true); - if (token.isIdent()) { - s += token.value; - if (isClass) - specificity.c++; - else - specificity.b++; - } - else - return null; - } - - else if (token.isSymbol(":")) { - s += token.value; - token = this.getToken(false, true); - if (token.isSymbol(":")) { - s += token.value; - token = this.getToken(false, true); - } - if (token.isIdent()) { - s += token.value; - if (this.isPseudoElement(token.value)) - specificity.d++; - else - specificity.c++; - } - else if (token.isFunction()) { - s += token.value; - if (token.isFunction(":not(")) { - if (!canNegate) - return null; - token = this.getToken(true, true); - var simpleSelector = this.parseSimpleSelector(token, isFirstInChain, false); - if (!simpleSelector) - return null; - else { - s += simpleSelector.selector; - token = this.getToken(true, true); - if (token.isSymbol(")")) - s += ")"; - else - return null; - } - specificity.c++; - } - else { - while (true) { - token = this.getToken(false, true); - if (token.isSymbol(")")) { - s += ")"; - break; - } else - s += token.value; - } - specificity.c++; - } - } else - return null; - - } else if (token.isSymbol("[")) { - s += "["; - token = this.getToken(true, true); - if (token.isIdent() || token.isSymbol("*")) { - s += token.value; - var nextToken = this.getToken(true, true); - if (nextToken.isSymbol("|")) { - s += "|"; - token = this.getToken(true, true); - if (token.isIdent()) - s += token.value; - else - return null; - } else - this.ungetToken(); - } else if (token.isSymbol("|")) { - s += "|"; - token = this.getToken(true, true); - if (token.isIdent()) - s += token.value; - else - return null; - } - else - return null; - - // nothing, =, *=, $=, ^=, |= - token = this.getToken(true, true); - if (token.isIncludes() - || token.isDashmatch() - || token.isBeginsmatch() - || token.isEndsmatch() - || token.isContainsmatch() - || token.isSymbol("=")) { - s += token.value; - token = this.getToken(true, true); - if (token.isString() || token.isIdent()) { - s += token.value; - token = this.getToken(true, true); - } - else - return null; - - if (token.isSymbol("]")) { - s += token.value; - specificity.c++; - } - else - return null; - } - else if (token.isSymbol("]")) { - s += token.value; - specificity.c++; - } - else - return null; - - } - else if (token.isWhiteSpace()) { - var t = this.lookAhead(true, true); - if (t.isSymbol('{')) - return "" - } - if (s) - return {selector: s, specificity: specificity }; - return null; -}; - -CSSParser.prototype.trim11 = function(str) { - str = str.replace(/^\s+/, ''); - for (var i = str.length - 1; i >= 0; i--) { - if (/\S/.test( str.charAt(i) )) { // XXX charat - str = str.substring(0, i + 1); - break; - } - } - return str; -}; - -CSSParser.prototype.parseStyleRule = function(aToken, aOwner, aIsInsideMediaRule) -{ - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - this.preserveState(); - // first let's see if we have a selector here... - var selector = this.parseSelector(aToken, false); - var valid = false; - var declarations = []; - if (selector) { - selector = this.trim11(selector.selector); - var s = selector; - var token = this.getToken(true, true); - if (token.isSymbol("{")) { - s += " { "; - var token = this.getToken(true, false); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, true, aOwner); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - } - else { - // selector is invalid so the whole rule is invalid with it - } - - if (valid) { - var rule = new jscsspStyleRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.mSelectorText = selector; - if (aIsInsideMediaRule) - rule.parentRule = aOwner; - else - rule.parentStyleSheet = aOwner; - aOwner.cssRules.push(rule); - return s; - } - this.restoreState(); - s = this.currentToken().value; - this.addUnknownAtRule(aOwner, s); - return ""; -}; - -CSSParser.prototype.parseTextShadows = function() -{ - var shadows = []; - var token = this.getToken(true, true); - var color = "", blurRadius = "0px", offsetX = "0px", offsetY = "0px"; - while (token.isNotNull()) { - if (token.isIdent("none")) { - shadows.push( { none: true } ); - token = this.getToken(true, true); - } - else { - if (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent()) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetX = token.value; - token = this.getToken(true, true); - } - else - return []; - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var offsetY = token.value; - token = this.getToken(true, true); - } - else - return []; - if (token.isPercentage() || - token.isDimensionOfUnit("cm") || - token.isDimensionOfUnit("mm") || - token.isDimensionOfUnit("in") || - token.isDimensionOfUnit("pc") || - token.isDimensionOfUnit("px") || - token.isDimensionOfUnit("em") || - token.isDimensionOfUnit("ex") || - token.isDimensionOfUnit("pt")) { - var blurRadius = token.value; - token = this.getToken(true, true); - } - if (!color && - (token.isFunction("rgb(") || - token.isFunction("rgba(") || - token.isFunction("hsl(") || - token.isFunction("hsla(") || - token.isSymbol("#") || - token.isIdent())) { - var color = this.parseColor(token); - token = this.getToken(true, true); - } - - shadows.push( { none: false, - color: color, - offsetX: offsetX, offsetY: offsetY, - blurRadius: blurRadius } ); - - if (token.isSymbol(",")) { - color = ""; - blurRadius = "0px"; - offsetX = "0px"; - offsetY = "0px"; - token = this.getToken(true, true); - } - else if (!token.isNotNull()) - return shadows; - else - return []; - } - } - return shadows; -}; - -CSSParser.prototype.currentToken = function() { - return this.mToken; -}; - -CSSParser.prototype.getHexValue = function() { - this.mToken = this.mScanner.nextHexValue(); - return this.mToken; -}; - -CSSParser.prototype.getToken = function(aSkipWS, aSkipComment) { - if (this.mLookAhead) { - this.mToken = this.mLookAhead; - this.mLookAhead = null; - return this.mToken; - } - - this.mToken = this.mScanner.nextToken(); - while (this.mToken && - ((aSkipWS && this.mToken.isWhiteSpace()) || - (aSkipComment && this.mToken.isComment()))) - this.mToken = this.mScanner.nextToken(); - return this.mToken; -}; - -CSSParser.prototype.lookAhead = function(aSkipWS, aSkipComment) { - var preservedToken = this.mToken; - this.mScanner.preserveState(); - var token = this.getToken(aSkipWS, aSkipComment); - this.mScanner.restoreState(); - this.mToken = preservedToken; - - return token; -}; - -CSSParser.prototype.ungetToken = function() { - this.mLookAhead = this.mToken; -}; - -CSSParser.prototype.addWhitespace = function(aSheet, aString) { - var rule = new jscsspWhitespace(); - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype.addComment = function(aSheet, aString) { - var rule = new jscsspComment(); - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype._createJscsspDeclaration = function(property, value) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - decl.value = this.trim11(value); - decl.parsedCssText = property + ": " + value + ";"; - return decl; -}; - -CSSParser.prototype._createJscsspDeclarationFromValue = function(property, valueText) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null); - value.value = valueText; - decl.values = [value]; - decl.valueText = valueText; - decl.parsedCssText = property + ": " + valueText + ";"; - return decl; -}; - -CSSParser.prototype._createJscsspDeclarationFromValuesArray = function(property, values, valueText) -{ - var decl = new jscsspDeclaration(); - decl.property = property; - decl.values = values; - decl.valueText = valueText; - decl.parsedCssText = property + ": " + valueText + ";"; - return decl; -}; - -CSSParser.prototype.preserveState = function() { - this.mPreservedTokens.push(this.currentToken()); - this.mScanner.preserveState(); -}; - -CSSParser.prototype.restoreState = function() { - if (this.mPreservedTokens.length) { - this.mScanner.restoreState(); - this.mToken = this.mPreservedTokens.pop(); - } -}; - -CSSParser.prototype.forgetState = function() { - if (this.mPreservedTokens.length) { - this.mScanner.forgetState(); - this.mPreservedTokens.pop(); - } -}; - -CSSParser.prototype.addUnknownAtRule = function(aSheet, aString) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var blocks = []; - var token = this.getToken(false, false); - while (token.isNotNull()) { - aString += token.value; - if (token.isSymbol(";") && !blocks.length) - break; - else if (token.isSymbol("{") - || token.isSymbol("(") - || token.isSymbol("[") - || token.type == "function") { - blocks.push(token.isFunction() ? "(" : token.value); - } else if (token.isSymbol("}") - || token.isSymbol(")") - || token.isSymbol("]")) { - if (blocks.length) { - var ontop = blocks[blocks.length - 1]; - if ((token.isSymbol("}") && ontop == "{") - || (token.isSymbol(")") && ontop == "(") - || (token.isSymbol("]") && ontop == "[")) { - blocks.pop(); - if (!blocks.length && token.isSymbol("}")) - break; - } - } - } - token = this.getToken(false, false); - } - - this.addUnknownRule(aSheet, aString, currentLine); -}; - -CSSParser.prototype.addUnknownRule = function(aSheet, aString, aCurrentLine) { - var errorMsg = this.consumeError(); - var rule = new jscsspErrorRule(errorMsg); - rule.currentLine = aCurrentLine; - rule.parsedCssText = aString; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule); -}; - -CSSParser.prototype.parseURL = function(token) -{ - var value = ""; - if (token.isString()) - { - value += token.value; - token = this.getToken(true, true); - } - else - while (true) - { - if (!token.isNotNull()) { - this.reportError(kURL_EOF); - return ""; - } - if (token.isWhiteSpace()) { - nextToken = this.lookAhead(true, true); - // if next token is not a closing parenthesis, that's an error - if (!nextToken.isSymbol(")")) { - this.reportError(kURL_WS_INSIDE); - token = this.currentToken(); - break; - } - } - if (token.isSymbol(")")) { - break; - } - value += token.value; - token = this.getToken(false, false); - } - - if (token.isSymbol(")")) { - return value + ")"; - } - return ""; -}; - -CSSParser.prototype.parseVariablesRule = function(token, aSheet) { - var currentLine = CountLF(this.mScanner.getAlreadyScanned()); - var s = token.value; - var declarations = []; - var valid = false; - this.preserveState(); - token = this.getToken(true, true); - var media = []; - var foundMedia = false; - while (token.isNotNull()) { - if (token.isIdent()) { - foundMedia = true; - s += " " + token.value; - media.push(token.value); - token = this.getToken(true, true); - if (token.isSymbol(",")) { - s += ","; - } else { - if (token.isSymbol("{")) - this.ungetToken(); - else { - // error... - token.type = jscsspToken.NULL_TYPE; - break; - } - } - } else if (token.isSymbol("{")) - break; - else if (foundMedia) { - token.type = jscsspToken.NULL_TYPE; - // not a media list - break; - } - token = this.getToken(true, true); - } - - if (token.isSymbol("{")) { - s += " {"; - token = this.getToken(true, true); - while (true) { - if (!token.isNotNull()) { - valid = true; - break; - } - if (token.isSymbol("}")) { - s += "}"; - valid = true; - break; - } else { - var d = this.parseDeclaration(token, declarations, true, false, aSheet); - s += ((d && declarations.length) ? " " : "") + d; - } - token = this.getToken(true, false); - } - } - if (valid) { - this.forgetState(); - var rule = new jscsspVariablesRule(); - rule.currentLine = currentLine; - rule.parsedCssText = s; - rule.declarations = declarations; - rule.media = media; - rule.parentStyleSheet = aSheet; - aSheet.cssRules.push(rule) - return true; - } - this.restoreState(); - return false; -}; - -function jscsspToken(aType, aValue, aUnit) -{ - this.type = aType; - this.value = aValue; - this.unit = aUnit; -} - -jscsspToken.NULL_TYPE = 0; - -jscsspToken.WHITESPACE_TYPE = 1; -jscsspToken.STRING_TYPE = 2; -jscsspToken.COMMENT_TYPE = 3; -jscsspToken.NUMBER_TYPE = 4; -jscsspToken.IDENT_TYPE = 5; -jscsspToken.FUNCTION_TYPE = 6; -jscsspToken.ATRULE_TYPE = 7; -jscsspToken.INCLUDES_TYPE = 8; -jscsspToken.DASHMATCH_TYPE = 9; -jscsspToken.BEGINSMATCH_TYPE = 10; -jscsspToken.ENDSMATCH_TYPE = 11; -jscsspToken.CONTAINSMATCH_TYPE = 12; -jscsspToken.SYMBOL_TYPE = 13; -jscsspToken.DIMENSION_TYPE = 14; -jscsspToken.PERCENTAGE_TYPE = 15; -jscsspToken.HEX_TYPE = 16; - -jscsspToken.prototype = { - - isNotNull: function () - { - return this.type; - }, - - _isOfType: function (aType, aValue) - { - return (this.type == aType && (!aValue || this.value.toLowerCase() == aValue)); - }, - - isWhiteSpace: function(w) - { - return this._isOfType(jscsspToken.WHITESPACE_TYPE, w); - }, - - isString: function() - { - return this._isOfType(jscsspToken.STRING_TYPE); - }, - - isComment: function() - { - return this._isOfType(jscsspToken.COMMENT_TYPE); - }, - - isNumber: function(n) - { - return this._isOfType(jscsspToken.NUMBER_TYPE, n); - }, - - isIdent: function(i) - { - return this._isOfType(jscsspToken.IDENT_TYPE, i); - }, - - isFunction: function(f) - { - return this._isOfType(jscsspToken.FUNCTION_TYPE, f); - }, - - isAtRule: function(a) - { - return this._isOfType(jscsspToken.ATRULE_TYPE, a); - }, - - isIncludes: function() - { - return this._isOfType(jscsspToken.INCLUDES_TYPE); - }, - - isDashmatch: function() - { - return this._isOfType(jscsspToken.DASHMATCH_TYPE); - }, - - isBeginsmatch: function() - { - return this._isOfType(jscsspToken.BEGINSMATCH_TYPE); - }, - - isEndsmatch: function() - { - return this._isOfType(jscsspToken.ENDSMATCH_TYPE); - }, - - isContainsmatch: function() - { - return this._isOfType(jscsspToken.CONTAINSMATCH_TYPE); - }, - - isSymbol: function(c) - { - return this._isOfType(jscsspToken.SYMBOL_TYPE, c); - }, - - isDimension: function() - { - return this._isOfType(jscsspToken.DIMENSION_TYPE); - }, - - isPercentage: function() - { - return this._isOfType(jscsspToken.PERCENTAGE_TYPE); - }, - - isHex: function() - { - return this._isOfType(jscsspToken.HEX_TYPE); - }, - - isDimensionOfUnit: function(aUnit) - { - return (this.isDimension() && this.unit == aUnit); - }, - - isLength: function() - { - return (this.isPercentage() || - this.isDimensionOfUnit("cm") || - this.isDimensionOfUnit("mm") || - this.isDimensionOfUnit("in") || - this.isDimensionOfUnit("pc") || - this.isDimensionOfUnit("px") || - this.isDimensionOfUnit("em") || - this.isDimensionOfUnit("ex") || - this.isDimensionOfUnit("pt")); - }, - - isAngle: function() - { - return (this.isDimensionOfUnit("deg") || - this.isDimensionOfUnit("rad") || - this.isDimensionOfUnit("grad")); - } -} - -/* kJscsspCHARSET_RULE */ - -function jscsspCharsetRule() -{ - this.type = kJscsspCHARSET_RULE; - this.encoding = null; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspCharsetRule.prototype = { - - cssText: function() { - return "@charset " + this.encoding + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - if (parser.parseCharsetRule(sheet)) { - var newRule = sheet.cssRules[0]; - this.encoding = newRule.encoding; - this.parsedCssText = newRule.parsedCssText; - return; - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspCOMMENT */ - -function jscsspComment() -{ - this.type = kJscsspCOMMENT; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspComment.prototype = { - cssText: function() { - return this.parsedCssText; - }, - - setCssText: function(val) { - var parser = new CSSParser(val); - var token = parser.getToken(true, false); - if (token.isComment()) - this.parsedCssText = token.value; - else - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspSTYLE_DECLARATION */ - -function jscsspDeclaration() -{ - this.type = kJscsspSTYLE_DECLARATION; - this.property = null; - this.values = []; - this.valueText = null; - this.priority = null; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspDeclaration.prototype = { - kCOMMA_SEPARATED: { - "cursor": true, - "font-family": true, - "voice-family": true, - "background-image": true - }, - - kUNMODIFIED_COMMA_SEPARATED_PROPERTIES: { - "text-shadow": true, - "box-shadow": true, - "-moz-transition": true, - "-moz-transition-property": true, - "-moz-transition-duration": true, - "-moz-transition-timing-function": true, - "-moz-transition-delay": true, - "src": true, - "-moz-font-feature-settings": true - }, - - cssText: function() { - var prefixes = PrefixHelper.prefixesForProperty(this.property); - - var rv = ""; - if (this.property in this.kUNMODIFIED_COMMA_SEPARATED_PROPERTIES) { - if (prefixes) { - rv = ""; - for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) { - var property = prefixes[propertyIndex]; - rv += (propertyIndex ? gTABS : "") + property + ": "; - rv += this.valueText + (this.priority ? " !important" : "") + ";"; - rv += ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : ""); - } - return rv; - } - return this.property + ": " + this.valueText + - (this.priority ? " !important" : "") + ";" - } - - if (prefixes) { - rv = ""; - for (var propertyIndex = 0; propertyIndex < prefixes.length; propertyIndex++) { - var property = prefixes[propertyIndex]; - rv += (propertyIndex ? gTABS : "") + property + ": "; - var separator = (property in this.kCOMMA_SEPARATED) ? ", " : " "; - for (var i = 0; i < this.values.length; i++) - if (this.values[i].cssText() != null) - rv += (i ? separator : "") + this.values[i].cssText(); - else - return null; - rv += (this.priority ? " !important" : "") + ";" + - ((prefixes.length > 1 && propertyIndex != prefixes.length -1) ? "\n" : ""); - } - return rv; - } - - var separator = (this.property in this.kCOMMA_SEPARATED) ? ", " : " "; - var extras = {"webkit": false, "presto": false, "trident": false, "gecko1.9.2": false, "generic": false } - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - var paren = v.indexOf("("); - var kwd = v; - if (paren != -1) - kwd = v.substr(0, paren); - if (kwd in kCSS_VENDOR_VALUES) { - for (var j in kCSS_VENDOR_VALUES[kwd]) { - extras[j] = extras[j] || (kCSS_VENDOR_VALUES[kwd][j] != ""); - } - } - } - else - return null; - } - - for (var j in extras) { - if (extras[j]) { - var str = "\n" + gTABS + this.property + ": "; - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - var paren = v.indexOf("("); - var kwd = v; - if (paren != -1) - kwd = v.substr(0, paren); - if (kwd in kCSS_VENDOR_VALUES) { - functor = kCSS_VENDOR_VALUES[kwd][j]; - if (functor) { - v = (typeof functor == "string") ? functor : functor(v, j); - if (!v) { - str = null; - break; - } - } - } - str += (i ? separator : "") + v; - } - else - return null; - } - if (str) - rv += str + ";" - else - rv += "\n" + gTABS + "/* Impossible to translate property " + this.property + " for " + j + " */"; - } - } - - rv += "\n" + gTABS + this.property + ": "; - for (var i = 0; i < this.values.length; i++) { - var v = this.values[i].cssText(); - if (v != null) { - rv += (i ? separator : "") + v; - } - } - rv += (this.priority ? " !important" : "") + ";"; - - return rv; - }, - - setCssText: function(val) { - var declarations = []; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (parser.parseDeclaration(token, declarations, true, true, null) - && declarations.length - && declarations[0].type == kJscsspSTYLE_DECLARATION) { - var newDecl = declarations.cssRules[0]; - this.property = newDecl.property; - this.value = newDecl.value; - this.priority = newDecl.priority; - this.parsedCssText = newRule.parsedCssText; - return; - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspUNKNOWN_RULE */ - -function jscsspErrorRule(aErrorMsg) -{ - this.error = aErrorMsg ? aErrorMsg : "INVALID"; - this.type = kJscsspUNKNOWN_RULE; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspErrorRule.prototype = { - cssText: function() { - return this.parsedCssText; - } -}; - -/* kJscsspFONT_FACE_RULE */ - -function jscsspFontFaceRule() -{ - this.type = kJscsspFONT_FACE_RULE; - this.parsedCssText = null; - this.descriptors = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspFontFaceRule.prototype = { - cssText: function() { - var rv = gTABS + "@font-face {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.descriptors.length; i++) - rv += gTABS + this.descriptors[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@font-face")) { - if (parser.parseFontFaceRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.descriptors = newRule.descriptors; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspIMPORT_RULE */ - -function jscsspImportRule() -{ - this.type = kJscsspIMPORT_RULE; - this.parsedCssText = null; - this.href = null; - this.media = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspImportRule.prototype = { - cssText: function() { - var mediaString = this.media.join(", "); - return "@import " + this.href - + ((mediaString && mediaString != "all") ? mediaString + " " : "") - + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@import")) { - if (parser.parseImportRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.href = newRule.href; - this.media = newRule.media; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspKEYFRAME_RULE */ -function jscsspKeyframeRule() -{ - this.type = kJscsspKEYFRAME_RULE; - this.parsedCssText = null; - this.declarations = [] - this.keyText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspKeyframeRule.prototype = { - cssText: function() { - var rv = this.keyText + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) { - var declText = this.declarations[i].cssText(); - if (declText) - rv += gTABS + this.declarations[i].cssText() + "\n"; - } - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - if (parser.parseKeyframeRule(token, sheet, false)) { - var newRule = sheet.cssRules[0]; - this.keyText = newRule.keyText; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspKEYFRAMES_RULE */ -function jscsspKeyframesRule() -{ - this.type = kJscsspKEYFRAMES_RULE; - this.parsedCssText = null; - this.cssRules = []; - this.name = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspKeyframesRule.prototype = { - cssText: function() { - var rv = ""; - var prefixes = ["moz", "webkit", "ms", "o", ""]; - for (var p = 0; p < prefixes.length; p++) { - rv += gTABS - + "@" + (prefixes[p] ? "-" + prefixes[p] + "-" : "") + "keyframes " - + this.name + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.cssRules.length; i++) - rv += gTABS + this.cssRules[i].cssText() + "\n"; - gTABS = preservedGTABS; - rv += gTABS + "}\n\n"; - } - return rv; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@-mozkeyframes")) { - if (parser.parseKeyframesRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.cssRules = newRule.cssRules; - this.name = newRule.name; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspMEDIA_RULE */ - -function jscsspMediaRule() -{ - this.type = kJscsspMEDIA_RULE; - this.parsedCssText = null; - this.cssRules = []; - this.media = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspMediaRule.prototype = { - cssText: function() { - var rv = gTABS + "@media " + this.media.join(", ") + " {\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.cssRules.length; i++) - rv += this.cssRules[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@media")) { - if (parser.parseMediaRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.cssRules = newRule.cssRules; - this.media = newRule.media; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspNAMESPACE_RULE */ - -function jscsspNamespaceRule() -{ - this.type = kJscsspNAMESPACE_RULE; - this.parsedCssText = null; - this.prefix = null; - this.url = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspNamespaceRule.prototype = { - cssText: function() { - return "@namespace " + (this.prefix ? this.prefix + " ": "") - + this.url - + ";"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@namespace")) { - if (parser.parseNamespaceRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.url = newRule.url; - this.prefix = newRule.prefix; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspPAGE_RULE */ - -function jscsspPageRule() -{ - this.type = kJscsspPAGE_RULE; - this.parsedCssText = null; - this.pageSelector = null; - this.declarations = []; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspPageRule.prototype = { - cssText: function() { - var rv = gTABS + "@page " - + (this.pageSelector ? this.pageSelector + " ": "") - + "{\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) - rv += gTABS + this.declarations[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@page")) { - if (parser.parsePageRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.pageSelector = newRule.pageSelector; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspSTYLE_RULE */ - -function jscsspStyleRule() -{ - this.type = kJscsspSTYLE_RULE; - this.parsedCssText = null; - this.declarations = [] - this.mSelectorText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspStyleRule.prototype = { - cssText: function() { - var rv = gTABS + this.mSelectorText + " {"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) { - var declText = this.declarations[i].cssText(); - if (declText) - rv += gTABS + this.declarations[i].cssText() ; - } - gTABS = preservedGTABS; - return rv + "\n" + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - if (parser.parseStyleRule(token, sheet, false)) { - var newRule = sheet.cssRules[0]; - this.mSelectorText = newRule.mSelectorText; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - }, - - selectorText: function() { - return this.mSelectorText; - }, - - setSelectorText: function(val) { - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (!token.isNotNull()) { - var s = parser.parseSelector(token, true); - if (s) { - this.mSelectorText = s.selector; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -function jscsspStylesheet() -{ - this.cssRules = []; - this.variables = {}; -} - -jscsspStylesheet.prototype = { - insertRule: function(aRule, aIndex) { - try { - this.cssRules.splice(aIndex, 1, aRule); - } - catch(e) { - } - }, - - deleteRule: function(aIndex) { - try { - this.cssRules.splice(aIndex); - } - catch(e) { - } - }, - - cssText: function() { - var rv = ""; - for (var i = 0; i < this.cssRules.length; i++) - rv += this.cssRules[i].cssText() + "\n\n"; - return rv; - } -}; - -var kJscsspINHERIT_VALUE = 0; -var kJscsspPRIMITIVE_VALUE = 1; -var kJscsspVARIABLE_VALUE = 4; - -function jscsspVariable(aType, aSheet) -{ - this.value = ""; - this.type = aType; - this.name = null; - this.parentRule = null; - this.parentStyleSheet = aSheet; -} - -jscsspVariable.prototype = { - cssText: function() { - if (this.type == kJscsspVARIABLE_VALUE) - return this.resolveVariable(this.name, this.parentRule, this.parentStyleSheet); - else - return this.value; - }, - - setCssText: function(val) { - if (this.type == kJscsspVARIABLE_VALUE) - throw DOMException.SYNTAX_ERR; - else - this.value = val; - }, - - resolveVariable: function(aName, aRule, aSheet) - { - return "var(" + aName + ")"; - } -}; - -/* kJscsspVARIABLES_RULE */ - -function jscsspVariablesRule() -{ - this.type = kJscsspVARIABLES_RULE; - this.parsedCssText = null; - this.declarations = []; - this.parentStyleSheet = null; - this.parentRule = null; - this.media = null; -} - -jscsspVariablesRule.prototype = { - cssText: function() { - var rv = gTABS + "@variables " + - (this.media.length ? this.media.join(", ") + " " : "") + - "{\n"; - var preservedGTABS = gTABS; - gTABS += " "; - for (var i = 0; i < this.declarations.length; i++) - rv += gTABS + this.declarations[i].cssText() + "\n"; - gTABS = preservedGTABS; - return rv + gTABS + "}"; - }, - - setCssText: function(val) { - var sheet = {cssRules: []}; - var parser = new CSSParser(val); - var token = parser.getToken(true, true); - if (token.isAtRule("@variables")) { - if (parser.parseVariablesRule(token, sheet)) { - var newRule = sheet.cssRules[0]; - this.declarations = newRule.declarations; - this.parsedCssText = newRule.parsedCssText; - return; - } - } - throw DOMException.SYNTAX_ERR; - } -}; - -/* kJscsspWHITE_SPACE */ - -function jscsspWhitespace() -{ - this.type = kJscsspWHITE_SPACE; - this.parsedCssText = null; - this.parentStyleSheet = null; - this.parentRule = null; -} - -jscsspWhitespace.prototype = { - cssText: function() { - return this.parsedCssText; - } -}; - -var kJscsspUNKNOWN_RULE = 0; -var kJscsspSTYLE_RULE = 1 -var kJscsspCHARSET_RULE = 2; -var kJscsspIMPORT_RULE = 3; -var kJscsspMEDIA_RULE = 4; -var kJscsspFONT_FACE_RULE = 5; -var kJscsspPAGE_RULE = 6; - -var kJscsspKEYFRAMES_RULE = 7; -var kJscsspKEYFRAME_RULE = 8; - -var kJscsspNAMESPACE_RULE = 100; -var kJscsspCOMMENT = 101; -var kJscsspWHITE_SPACE = 102; - -var kJscsspVARIABLES_RULE = 200; - -var kJscsspSTYLE_DECLARATION = 1000; - -var gTABS = ""; - diff --git a/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js b/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js deleted file mode 100644 index 3cecd957dc..0000000000 --- a/src/Umbraco.Web.UI.Client/lib/less/less-1.7.0.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * LESS - Leaner CSS v1.7.0 - * http://lesscss.org - * - * Copyright (c) 2009-2014, Alexis Sellier - * Licensed under the Apache v2 License. - * - */ - - /** * @license Apache v2 - */ - -!function(a,b){function c(b){return a.less[b.split("/")[1]]}function d(a,b){"undefined"!=typeof console&&w.logLevel>=b&&console.log("less: "+a)}function e(a){return a.replace(/^[a-z-]+:\/+?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function f(a,c){var e="{line} {content}",f=a.filename||c,g=[],h=(a.type||"Syntax")+"Error: "+(a.message||"There is an error in your .less file")+" in "+f+" ",i=function(a,c,d){a.extract[c]!==b&&g.push(e.replace(/\{line\}/,(parseInt(a.line,10)||0)+(c-1)).replace(/\{class\}/,d).replace(/\{content\}/,a.extract[c]))};a.extract?(i(a,0,""),i(a,1,"line"),i(a,2,""),h+="on line "+a.line+", column "+(a.column+1)+":\n"+g.join("\n")):a.stack&&(h+=a.stack),d(h,z.errors)}function g(a,b,c){var f=b.href||"",g="less:"+(b.title||e(f)),h=document.getElementById(g),i=!1,j=document.createElement("style");if(j.setAttribute("type","text/css"),b.media&&j.setAttribute("media",b.media),j.id=g,j.styleSheet)try{j.styleSheet.cssText=a}catch(k){throw new Error("Couldn't reassign styleSheet.cssText.")}else j.appendChild(document.createTextNode(a)),i=null!==h&&h.childNodes.length>0&&j.childNodes.length>0&&h.firstChild.nodeValue===j.firstChild.nodeValue;var l=document.getElementsByTagName("head")[0];if(null===h||i===!1){var m=b&&b.nextSibling||null;m?m.parentNode.insertBefore(j,m):l.appendChild(j)}if(h&&i===!1&&h.parentNode.removeChild(h),c&&D){d("saving "+f+" to cache.",z.info);try{D.setItem(f,a),D.setItem(f+":timestamp",c)}catch(k){d("failed to save",z.errors)}}}function h(a){return w.postProcessor&&"function"==typeof w.postProcessor&&(a=w.postProcessor.call(a,a)||a),a}function i(a,c){var d,f,h="less-error-message:"+e(c||""),i='
  • {content}
  • ',j=document.createElement("div"),k=[],l=a.filename||c,m=l.match(/([^\/]+(\?.*)?)$/)[1];j.id=h,j.className="less-error-message",f="

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

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

      "+k.join("")+"
    "):a.stack&&(f+="
    "+a.stack.split("\n").slice(1).join("
    ")),j.innerHTML=f,g([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),j.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),"development"==w.env&&(d=setInterval(function(){document.body&&(document.getElementById(h)?document.body.replaceChild(j,document.getElementById(h)):document.body.insertBefore(j,document.body.firstChild),clearInterval(d))},10))}function j(a,b){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?f(a,b):"function"==typeof w.errorReporting&&w.errorReporting("add",a,b):i(a,b)}function k(a){var b=document.getElementById("less-error-message:"+e(a));b&&b.parentNode.removeChild(b)}function l(){}function m(a){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?l(a):"function"==typeof w.errorReporting&&w.errorReporting("remove",a):k(a)}function n(a){for(var b,c=document.getElementsByTagName("style"),d=0;d0&&(h.splice(c-1,2),c-=2)}return g.hostPart=f[1],g.directories=h,g.path=f[1]+h.join("/"),g.fileUrl=g.path+(f[4]||""),g.url=g.fileUrl+(f[5]||""),g}function p(a,b){var c,d,e,f,g=o(a),h=o(b),i="";if(g.hostPart!==h.hostPart)return"";for(d=Math.max(h.directories.length,g.directories.length),c=0;d>c&&h.directories[c]===g.directories[c];c++);for(f=h.directories.slice(c),e=g.directories.slice(c),c=0;c=200&&b.status<300?c(b.responseText,b.getResponseHeader("Last-Modified")):"function"==typeof d&&d(b.status,a)}var g=q(),h=y?w.fileAsync:w.async;"function"==typeof g.overrideMimeType&&g.overrideMimeType("text/css"),d("XHR: Getting '"+a+"'",z.debug),g.open("GET",a,h),g.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5"),g.send(null),y&&!w.fileAsync?0===g.status||g.status>=200&&g.status<300?c(g.responseText):e(g.status,a):h?g.onreadystatechange=function(){4==g.readyState&&f(g,c,e)}:f(g,c,e)}function s(b,c,d,e){c&&c.currentDirectory&&!/^([a-z-]+:)?\//.test(b)&&(b=c.currentDirectory+b);var f=o(b,a.location.href),g=f.url,h={currentDirectory:f.path,filename:g};if(c?(h.entryPath=c.entryPath,h.rootpath=c.rootpath,h.rootFilename=c.rootFilename,h.relativeUrls=c.relativeUrls):(h.entryPath=f.path,h.rootpath=w.rootpath||f.path,h.rootFilename=g,h.relativeUrls=e.relativeUrls),h.relativeUrls&&(h.rootpath=e.rootpath?o(e.rootpath+p(f.path,h.entryPath)).path:f.path),e.useFileCache&&E[g])try{var i=E[g];d(null,i,g,h,{lastModified:new Date})}catch(j){d(j,null,g)}else r(g,e.mime,function(a,b){E[g]=a;try{d(null,a,g,h,{lastModified:b})}catch(c){d(c,null,g)}},function(a,b){d({type:"File",message:"'"+b+"' wasn't found ("+a+")"},null,g)})}function t(a,b,c,d,e){var f=new w.tree.parseEnv(w);f.mime=a.type,(e||w.globalVars)&&(f.useFileCache=!0),s(a.href,null,function(h,i,j,k,l){if(l){l.remaining=d;var n=D&&D.getItem(j),o=D&&D.getItem(j+":timestamp");if(!c&&o&&l.lastModified&&new Date(l.lastModified).valueOf()===new Date(o).valueOf())return g(n,a),l.local=!0,void b(null,null,i,a,l,j)}m(j),i?(f.currentFileInfo=k,new w.Parser(f).parse(i,function(c,d){if(c)return b(c,null,null,a);try{b(c,d,i,a,l,j)}catch(c){b(c,null,null,a)}},{modifyVars:e,globalVars:w.globalVars})):b(h,null,null,a,l,j)},f,e)}function u(a,b,c){for(var d=0;dD&&(C=C.slice(y-D),D=y)}function h(a,b){var c=a.charCodeAt(0|b);return 32>=c&&(32===c||10===c||9===c)}function i(a){var b,c,d=typeof a;return"string"===d?v.charAt(y)!==a?null:(l(1),a):(g(),(b=a.exec(C))?(c=b[0].length,l(c),"string"==typeof b?b:1===b.length?b[0]:b):null)}function j(a){y>D&&(C=C.slice(y-D),D=y);var b=a.exec(C);return b?(l(b[0].length),"string"==typeof b?b:1===b.length?b[0]:b):null}function k(a){return v.charAt(y)!==a?null:(l(1),a)}function l(a){for(var b,c=y,d=z,e=y-D,f=y+C.length-e,g=y+=a,h=v;f>y&&(b=h.charCodeAt(y),!(b>32))&&(32===b||10===b||9===b||13===b);y++);return C=C.slice(a+y-g+e),D=y,!C.length&&z=0&&"\n"!==b.charAt(c);)e++;return"number"==typeof a&&(d=(b.slice(0,a).match(/\n/g)||"").length),{line:d,column:e}}function t(a,b,d){var e=d.currentFileInfo.filename;return"browser"!==w.mode&&"rhino"!==w.mode&&(e=c("path").resolve(e)),{lineNumber:s(a,b).line+1,fileName:e}}function u(a,b){var c=r(a,b),d=s(a.index,c),e=d.line,f=d.column,g=a.call&&s(a.call,c).line,h=c.split("\n");this.type=a.type||"Syntax",this.message=a.message,this.filename=a.filename||b.currentFileInfo.filename,this.index=a.index,this.line="number"==typeof e?e+1:null,this.callLine=g+1,this.callExtract=h[g],this.stack=a.stack,this.column=f,this.extract=[h[e-1],h[e],h[e+1]]}var v,y,z,A,B,C,D,E,F,G=[],H=a&&a.filename;a instanceof x.parseEnv||(a=new x.parseEnv(a));var I=this.imports={paths:a.paths||[],queue:[],files:a.files,contents:a.contents,contentsIgnoredChars:a.contentsIgnoredChars,mime:a.mime,error:null,push:function(b,c,d,e){var f=this;this.queue.push(b);var g=function(a,c,d){f.queue.splice(f.queue.indexOf(b),1);var g=d===H;f.files[d]=c,a&&!f.error&&(f.error=a),e(a,c,g,d)};w.Parser.importer?w.Parser.importer(b,c,g,a):w.Parser.fileLoader(b,c,function(b,e,f,h){if(b)return void g(b);var i=new x.parseEnv(a);i.currentFileInfo=h,i.processImports=!1,i.contents[f]=e,(c.reference||d.reference)&&(h.reference=!0),d.inline?g(null,e,f):new w.Parser(i).parse(e,function(a,b){g(a,b,f)})},a)}},J=j;return u.prototype=new Error,u.prototype.constructor=u,this.env=a=a||{},this.optimization="optimization"in this.env?this.env.optimization:1,E={imports:I,parse:function(d,e,f){var g,h,i,j,k,l=null,m="";if(y=z=D=A=0,j=f&&f.globalVars?w.Parser.serializeVars(f.globalVars)+"\n":"",k=f&&f.modifyVars?"\n"+w.Parser.serializeVars(f.modifyVars):"",(j||f&&f.banner)&&(m=(f&&f.banner?f.banner:"")+j,E.imports.contentsIgnoredChars[a.currentFileInfo.filename]=m.length),d=d.replace(/\r\n/g,"\n"),v=d=m+d.replace(/^\uFEFF/,"")+k,E.imports.contents[a.currentFileInfo.filename]=d,B=function(b){function c(b,c){l=new u({index:c||i,type:"Parse",message:b,filename:a.currentFileInfo.filename},a)}function d(a){var c=i-s;512>c&&!a||!c||(r.push(b.slice(s,i+1)),s=i+1)}var e,f,g,h,i,j,k,m,n,o=b.length,p=0,q=0,r=[],s=0;for(i=0;o>i;i++)if(k=b.charCodeAt(i),!(k>=97&&122>=k||34>k))switch(k){case 40:q++,f=i;continue;case 41:if(--q<0)return c("missing opening `(`");continue;case 59:q||d();continue;case 123:p++,e=i;continue;case 125:if(--p<0)return c("missing opening `{`");p||q||d();continue;case 92:if(o-1>i){i++;continue}return c("unescaped `\\`");case 34:case 39:case 96:for(n=0,j=i,i+=1;o>i;i++)if(m=b.charCodeAt(i),!(m>96)){if(m==k){n=1;break}if(92==m){if(i==o-1)return c("unescaped `\\`");i++}}if(n)continue;return c("unmatched `"+String.fromCharCode(k)+"`",j);case 47:if(q||i==o-1)continue;if(m=b.charCodeAt(i+1),47==m)for(i+=2;o>i&&(m=b.charCodeAt(i),!(13>=m)||10!=m&&13!=m);i++);else if(42==m){for(g=j=i,i+=2;o-1>i&&(m=b.charCodeAt(i),125==m&&(h=i),42!=m||47!=b.charCodeAt(i+1));i++);if(i==o-1)return c("missing closing `*/`",j);i++}continue;case 42:if(o-1>i&&47==b.charCodeAt(i+1))return c("unmatched `/*`");continue}return 0!==p?g>e&&h>g?c("missing closing `}` or `*/`",e):c("missing closing `}`",e):0!==q?c("missing closing `)`",f):(d(!0),r)}(d),l)return e(new u(l,a));C=B[0];try{g=new x.Ruleset(null,this.parsers.primary()),g.root=!0,g.firstRoot=!0}catch(n){return e(new u(n,a))}if(g.toCSS=function(d){return function(e,f){e=e||{};var g,h,i=new x.evalEnv(e);"object"!=typeof f||Array.isArray(f)||(f=Object.keys(f).map(function(a){var b=f[a];return b instanceof x.Value||(b instanceof x.Expression||(b=new x.Expression([b])),b=new x.Value([b])),new x.Rule("@"+a,b,!1,null,0)}),i.frames=[new x.Ruleset(null,f)]);try{var j,k=[],l=[new x.joinSelectorVisitor,new x.processExtendsVisitor,new x.toCSSVisitor({compress:Boolean(e.compress)})],m=this;if(e.plugins)for(j=0;j57||43>b||47===b||44==b))return a=j(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/),a?new x.Dimension(a[1],a[2]):void 0},unicodeDescriptor:function(){var a;return a=j(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/),a?new x.UnicodeDescriptor(a[0]):void 0},javascript:function(){var c,d,e=y;return"~"===v.charAt(e)&&(e++,d=!0),"`"===v.charAt(e)?(a.javascriptEnabled===b||a.javascriptEnabled||o("You are using JavaScript, which has been disabled."),d&&k("~"),c=j(/^`([^`]*)`/),c?new x.JavaScript(c[1],y,d):void 0):void 0}},variable:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*:/))?a[1]:void 0},rulesetCall:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*\(\s*\)\s*;/))?new x.RulesetCall(a[1]):void 0},extend:function(a){var b,c,d,e,f,g=y;if(j(a?/^&:extend\(/:/^:extend\(/)){do{for(d=null,b=null;!(d=j(/^(all)(?=\s*(\)|,))/))&&(c=this.element());)b?b.push(c):b=[c];d=d&&d[1],f=new x.Extend(new x.Selector(b),d,g),e?e.push(f):e=[f]}while(k(","));return m(/^\)/),a&&m(/^;/),e}},extendRule:function(){return this.extend(!0)},mixin:{call:function(){var b,c,g,h,i,l,m=v.charAt(y),o=!1,p=y;if("."===m||"#"===m){for(d();;){if(b=y,h=j(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/),!h)break;g=new x.Element(i,h,b,a.currentFileInfo),c?c.push(g):c=[g],i=k(">")}return c&&(k("(")&&(l=this.args(!0).args,n(")")),F.important()&&(o=!0),F.end())?(f(),new x.mixin.Call(c,l,p,a.currentFileInfo,o)):void e()}},args:function(a){var b,c,g,h,i,l,m=E.parsers,n=m.entities,p={args:null,variadic:!1},q=[],r=[],s=[];for(d();;){if(a)l=m.detachedRuleset()||m.expression();else{if(m.comments(),"."===v.charAt(y)&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({variadic:!0});break}l=n.variable()||n.literal()||n.keyword()}if(!l)break;h=null,l.throwAwayComments&&l.throwAwayComments(),i=l;var t=null;if(a?l.value&&1==l.value.length&&(t=l.value[0]):t=l,t&&t instanceof x.Variable)if(k(":")){if(q.length>0&&(b&&o("Cannot mix ; and , as delimiter types"),c=!0),i=a&&m.detachedRuleset()||m.expression(),!i){if(!a)return e(),p.args=[],p;o("could not understand value for named argument")}h=g=t.name}else{if(!a&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({name:l.name,variadic:!0});break}a||(g=h=t.name,i=null)}i&&q.push(i),s.push({name:h,value:i}),k(",")||(k(";")||b)&&(c&&o("Cannot mix ; and , as delimiter types"),b=!0,q.length>1&&(i=new x.Value(q)),r.push({name:g,value:i}),g=null,q=[],c=!1)}return f(),p.args=b?r:s,p},definition:function(){var a,b,c,g,h=[],i=!1;if(!("."!==v.charAt(y)&&"#"!==v.charAt(y)||p(/^[^{]*\}/)))if(d(),b=j(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){a=b[1];var l=this.args(!1);if(h=l.args,i=l.variadic,!k(")"))return A=y,void e();if(F.comments(),j(/^when/)&&(g=m(F.conditions,"expected condition")),c=F.block())return f(),new x.mixin.Definition(a,h,c,g,i);e()}else f()}},entity:function(){var a=this.entities;return a.literal()||a.variable()||a.url()||a.call()||a.keyword()||a.javascript()||this.comment()},end:function(){return k(";")||q("}")},alpha:function(){var a;if(j(/^\(opacity=/i))return a=j(/^\d+/)||this.entities.variable(),a?(n(")"),new x.Alpha(a)):void 0},element:function(){var b,c,g,h=y;return c=this.combinator(),b=j(/^(?:\d+\.\d+|\d+)%/)||j(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||k("*")||k("&")||this.attribute()||j(/^\([^()@]+\)/)||j(/^[\.#](?=@)/)||this.entities.variableCurly(),b||(d(),k("(")?(g=this.selector())&&k(")")?(b=new x.Paren(g),f()):e():f()),b?new x.Element(c,b,h,a.currentFileInfo):void 0},combinator:function(){var a=v.charAt(y);if(">"===a||"+"===a||"~"===a||"|"===a||"^"===a){for(y++,"^"===v.charAt(y)&&(a="^^",y++);h(v,y);)y++;return new x.Combinator(a)}return new x.Combinator(h(v,y-1)?" ":null)},lessSelector:function(){return this.selector(!0)},selector:function(b){for(var c,d,e,f,g,h,i,j=y,k=J;(b&&(g=this.extend())||b&&(h=k(/^when/))||(f=this.element()))&&(h?i=m(this.conditions,"expected condition"):i?o("CSS guard can only be used at the end of selector"):g?d?d.push(g):d=[g]:(d&&o("Extend can only be used at the end of selector"),e=v.charAt(y),c?c.push(f):c=[f],f=null),"{"!==e&&"}"!==e&&";"!==e&&","!==e&&")"!==e););return c?new x.Selector(c,d,i,j,a.currentFileInfo):void(d&&o("Extend must be used to extend a selector, it cannot be used on its own"))},attribute:function(){if(k("[")){var a,b,c,d=this.entities;return(a=d.variableCurly())||(a=m(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/)),c=j(/^[|~*$^]?=/),c&&(b=d.quoted()||j(/^[0-9]+%/)||j(/^[\w-]+/)||d.variableCurly()),n("]"),new x.Attribute(a,c,b)}},block:function(){var a;return k("{")&&(a=this.primary())&&k("}")?a:void 0},blockRuleset:function(){var a=this.block();return a&&(a=new x.Ruleset(null,a)),a},detachedRuleset:function(){var a=this.blockRuleset();return a?new x.DetachedRuleset(a):void 0},ruleset:function(){var b,c,g,h;for(d(),a.dumpLineNumbers&&(h=t(y,v,a));;){if(c=this.lessSelector(),!c)break;if(b?b.push(c):b=[c],this.comments(),c.condition&&b.length>1&&o("Guards are only currently allowed on a single selector."),!k(","))break;c.condition&&o("Guards are only currently allowed on a single selector."),this.comments()}if(b&&(g=this.block())){f();var i=new x.Ruleset(b,g,a.strictImports);return a.dumpLineNumbers&&(i.debugInfo=h),i}A=y,e()},rule:function(b){var c,g,h,i,j,k=y,l=v.charAt(k);if("."!==l&&"#"!==l&&"&"!==l)if(d(),c=this.variable()||this.ruleProperty()){if(j="string"==typeof c,j&&(g=this.detachedRuleset()),g||(g=b||!a.compress&&!j?this.anonymousValue()||this.value():this.value()||this.anonymousValue(),h=this.important(),i=!j&&c.pop().value),g&&this.end())return f(),new x.Rule(c,g,h,i,k,a.currentFileInfo);if(A=y,e(),g&&!b)return this.rule(!0)}else f()},anonymousValue:function(){var a;return a=/^([^@+\/'"*`(;{}-]*);/.exec(C),a?(y+=a[0].length-1,new x.Anonymous(a[1])):void 0},"import":function(){var b,c,g=y;d();var h=j(/^@import?\s+/),i=(h?this.importOptions():null)||{};return h&&(b=this.entities.quoted()||this.entities.url())&&(c=this.mediaFeatures(),k(";"))?(f(),c=c&&new x.Value(c),new x.Import(b,c,i,g,a.currentFileInfo)):void e()},importOptions:function(){var a,b,c,d={};if(!k("("))return null;do if(a=this.importOption()){switch(b=a,c=!0,b){case"css":b="less",c=!1;break;case"once":b="multiple",c=!1}if(d[b]=c,!k(","))break}while(a);return n(")"),d},importOption:function(){var a=j(/^(less|css|multiple|once|inline|reference)/);return a?a[1]:void 0},mediaFeature:function(){var b,c,d=this.entities,e=[];do if(b=d.keyword()||d.variable())e.push(b);else if(k("(")){if(c=this.property(),b=this.value(),!k(")"))return null;if(c&&b)e.push(new x.Paren(new x.Rule(c,b,null,null,y,a.currentFileInfo,!0)));else{if(!b)return null;e.push(new x.Paren(b))}}while(b);return e.length>0?new x.Expression(e):void 0},mediaFeatures:function(){var a,b=this.entities,c=[];do if(a=this.mediaFeature()){if(c.push(a),!k(","))break}else if(a=b.variable(),a&&(c.push(a),!k(",")))break;while(a);return c.length>0?c:null},media:function(){var b,c,d,e;return a.dumpLineNumbers&&(e=t(y,v,a)),j(/^@media/)&&(b=this.mediaFeatures(),c=this.block())?(d=new x.Media(c,b,y,a.currentFileInfo),a.dumpLineNumbers&&(d.debugInfo=e),d):void 0},directive:function(){var b,c,g,h,i,l,m,n=y,p=!0;if("@"===v.charAt(y)){if(c=this["import"]()||this.media())return c;if(d(),b=j(/^@[a-z-]+/)){switch(h=b,"-"==b.charAt(1)&&b.indexOf("-",2)>0&&(h="@"+b.slice(b.indexOf("-",2)+1)),h){case"@charset":i=!0,p=!1;break;case"@namespace":l=!0,p=!1;break;case"@keyframes":i=!0;break;case"@host":case"@page":case"@document":case"@supports":m=!0}return i?(c=this.entity(),c||o("expected "+b+" identifier")):l?(c=this.expression(),c||o("expected "+b+" expression")):m&&(c=(j(/^[^{;]+/)||"").trim(),c&&(c=new x.Anonymous(c))),p&&(g=this.blockRuleset()),g||!p&&c&&k(";")?(f(),new x.Directive(b,c,g,n,a.currentFileInfo,a.dumpLineNumbers?t(n,v,a):null)):void e()}}},value:function(){var a,b=[];do if(a=this.expression(),a&&(b.push(a),!k(",")))break;while(a);return b.length>0?new x.Value(b):void 0},important:function(){return"!"===v.charAt(y)?j(/^! *important/):void 0},sub:function(){var a,b;return k("(")&&(a=this.addition())?(b=new x.Expression([a]),n(")"),b.parens=!0,b):void 0},multiplication:function(){var a,b,c,d,e;if(a=this.operand()){for(e=h(v,y-1);;){if(p(/^\/[*\/]/))break;if(c=k("/")||k("*"),!c)break;if(b=this.operand(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new x.Operation(c,[d||a,b],e),e=h(v,y-1)}return d||a}},addition:function(){var a,b,c,d,e;if(a=this.multiplication()){for(e=h(v,y-1);;){if(c=j(/^[-+]\s+/)||!e&&(k("+")||k("-")),!c)break;if(b=this.multiplication(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new x.Operation(c,[d||a,b],e),e=h(v,y-1)}return d||a}},conditions:function(){var a,b,c,d=y;if(a=this.condition()){for(;;){if(!p(/^,\s*(not\s*)?\(/)||!k(","))break;if(b=this.condition(),!b)break;c=new x.Condition("or",c||a,b,d)}return c||a}},condition:function(){var a,b,c,d,e=this.entities,f=y,g=!1;return j(/^not/)&&(g=!0),n("("),a=this.addition()||e.keyword()||e.quoted(),a?(d=j(/^(?:>=|<=|=<|[<=>])/),d?(b=this.addition()||e.keyword()||e.quoted(),b?c=new x.Condition(d,a,b,f,g):o("expected expression")):c=new x.Condition("=",a,new x.Keyword("true"),f,g),n(")"),j(/^and/)?new x.Condition("and",c,this.condition()):c):void 0},operand:function(){var a,b=this.entities,c=v.charAt(y+1);"-"!==v.charAt(y)||"@"!==c&&"("!==c||(a=k("-"));var d=this.sub()||b.dimension()||b.color()||b.variable()||b.call();return a&&(d.parensInOp=!0,d=new x.Negative(d)),d},expression:function(){var a,b,c=[];do a=this.addition()||this.entity(),a&&(c.push(a),p(/^\/[\/*]/)||(b=k("/"),b&&c.push(new x.Anonymous(b))));while(a);return c.length>0?new x.Expression(c):void 0},property:function(){var a=j(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);return a?a[1]:void 0},ruleProperty:function(){function b(a){var b=a.exec(e);return b?(g.push(y+h),h+=b[0].length,e=e.slice(b[1].length),f.push(b[1])):void 0}var c,d,e=C,f=[],g=[],h=0;for(b(/^(\*?)/);b(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/););if(f.length>1&&b(/^\s*((?:\+_|\+)?)\s*:/)){for(l(h),""===f[0]&&(f.shift(),g.shift()),d=0;dl;l++)e=b.rgb[l]/255,f=c.rgb[l]/255,h=a(e,f),g&&(h=(j*f+i*(e-j*(e+f-h)))/g),k[l]=255*h;return new d.Color(k,g)}function g(){var a,b=d.functions;for(a in l)l.hasOwnProperty(a)&&(b[a]=e.bind(null,Math[a],l[a]));for(a in m)m.hasOwnProperty(a)&&(b[a]=f.bind(null,m[a]));a=d.defaultFunc,b["default"]=a.eval.bind(a)}function h(a){return d.functions.hsla(a.h,a.s,a.l,a.a)}function i(a,b){return a instanceof d.Dimension&&a.unit.is("%")?parseFloat(a.value*b/100):j(a)}function j(a){if(a instanceof d.Dimension)return parseFloat(a.unit.is("%")?a.value/100:a.value);if("number"==typeof a)return a;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function k(a){return Math.min(1,Math.max(0,a))}d.functions={rgb:function(a,b,c){return this.rgba(a,b,c,1)},rgba:function(a,b,c,e){var f=[a,b,c].map(function(a){return i(a,255)});return e=j(e),new d.Color(f,e)},hsl:function(a,b,c){return this.hsla(a,b,c,1)},hsla:function(a,b,c,d){function e(a){return a=0>a?a+1:a>1?a-1:a,1>6*a?g+(f-g)*a*6:1>2*a?f:2>3*a?g+(f-g)*(2/3-a)*6:g}a=j(a)%360/360,b=k(j(b)),c=k(j(c)),d=k(j(d));var f=.5>=c?c*(b+1):c+b-c*b,g=2*c-f;return this.rgba(255*e(a+1/3),255*e(a),255*e(a-1/3),d)},hsv:function(a,b,c){return this.hsva(a,b,c,1)},hsva:function(a,b,c,d){a=j(a)%360/360*360,b=j(b),c=j(c),d=j(d);var e,f;e=Math.floor(a/60%6),f=a/60-e;var g=[c,c*(1-b),c*(1-f*b),c*(1-(1-f)*b)],h=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(255*g[h[e][0]],255*g[h[e][1]],255*g[h[e][2]],d)},hue:function(a){return new d.Dimension(Math.round(a.toHSL().h))},saturation:function(a){return new d.Dimension(Math.round(100*a.toHSL().s),"%")},lightness:function(a){return new d.Dimension(Math.round(100*a.toHSL().l),"%")},hsvhue:function(a){return new d.Dimension(Math.round(a.toHSV().h))},hsvsaturation:function(a){return new d.Dimension(Math.round(100*a.toHSV().s),"%")},hsvvalue:function(a){return new d.Dimension(Math.round(100*a.toHSV().v),"%")},red:function(a){return new d.Dimension(a.rgb[0])},green:function(a){return new d.Dimension(a.rgb[1])},blue:function(a){return new d.Dimension(a.rgb[2])},alpha:function(a){return new d.Dimension(a.toHSL().a)},luma:function(a){return new d.Dimension(Math.round(a.luma()*a.alpha*100),"%")},luminance:function(a){var b=.2126*a.rgb[0]/255+.7152*a.rgb[1]/255+.0722*a.rgb[2]/255;return new d.Dimension(Math.round(b*a.alpha*100),"%")},saturate:function(a,b){if(!a.rgb)return null;var c=a.toHSL();return c.s+=b.value/100,c.s=k(c.s),h(c)},desaturate:function(a,b){var c=a.toHSL();return c.s-=b.value/100,c.s=k(c.s),h(c)},lighten:function(a,b){var c=a.toHSL();return c.l+=b.value/100,c.l=k(c.l),h(c)},darken:function(a,b){var c=a.toHSL();return c.l-=b.value/100,c.l=k(c.l),h(c)},fadein:function(a,b){var c=a.toHSL();return c.a+=b.value/100,c.a=k(c.a),h(c)},fadeout:function(a,b){var c=a.toHSL();return c.a-=b.value/100,c.a=k(c.a),h(c)},fade:function(a,b){var c=a.toHSL();return c.a=b.value/100,c.a=k(c.a),h(c)},spin:function(a,b){var c=a.toHSL(),d=(c.h+b.value)%360;return c.h=0>d?360+d:d,h(c)},mix:function(a,b,c){c||(c=new d.Dimension(50));var e=c.value/100,f=2*e-1,g=a.toHSL().a-b.toHSL().a,h=((f*g==-1?f:(f+g)/(1+f*g))+1)/2,i=1-h,j=[a.rgb[0]*h+b.rgb[0]*i,a.rgb[1]*h+b.rgb[1]*i,a.rgb[2]*h+b.rgb[2]*i],k=a.alpha*e+b.alpha*(1-e);return new d.Color(j,k)},greyscale:function(a){return this.desaturate(a,new d.Dimension(100))},contrast:function(a,b,c,d){if(!a.rgb)return null;if("undefined"==typeof c&&(c=this.rgba(255,255,255,1)),"undefined"==typeof b&&(b=this.rgba(0,0,0,1)),b.luma()>c.luma()){var e=c;c=b,b=e}return d="undefined"==typeof d?.43:j(d),a.luma()i.value)&&(m[f]=g);else{if(k!==b&&j!==k)throw{type:"Argument",message:"incompatible types"};n[j]=m.length,m.push(g)}else Array.isArray(c[e].value)&&Array.prototype.push.apply(c,Array.prototype.slice.call(c[e].value));return 1==m.length?m[0]:(c=m.map(function(a){return a.toCSS(this.env)}).join(this.env.compress?",":", "),new d.Anonymous((a?"min":"max")+"("+c+")"))},min:function(){return this._minmax(!0,arguments)},max:function(){return this._minmax(!1,arguments)},"get-unit":function(a){return new d.Anonymous(a.unit)},argb:function(a){return new d.Anonymous(a.toARGB())},percentage:function(a){return new d.Dimension(100*a.value,"%")},color:function(a){if(a instanceof d.Quoted){var b,c=a.value;if(b=d.Color.fromKeyword(c))return b;if(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(c))return new d.Color(c.slice(1));throw{type:"Argument",message:"argument must be a color keyword or 3/6 digit hex e.g. #FFF"}}throw{type:"Argument",message:"argument must be a string"}},iscolor:function(a){return this._isa(a,d.Color)},isnumber:function(a){return this._isa(a,d.Dimension)},isstring:function(a){return this._isa(a,d.Quoted)},iskeyword:function(a){return this._isa(a,d.Keyword)},isurl:function(a){return this._isa(a,d.URL)},ispixel:function(a){return this.isunit(a,"px")},ispercentage:function(a){return this.isunit(a,"%")},isem:function(a){return this.isunit(a,"em")},isunit:function(a,b){return a instanceof d.Dimension&&a.unit.is(b.value||b)?d.True:d.False},_isa:function(a,b){return a instanceof b?d.True:d.False},tint:function(a,b){return this.mix(this.rgb(255,255,255),a,b)},shade:function(a,b){return this.mix(this.rgb(0,0,0),a,b)},extract:function(a,b){return b=b.value-1,Array.isArray(a.value)?a.value[b]:Array(a)[b]},length:function(a){var b=Array.isArray(a.value)?a.value.length:1;return new d.Dimension(b)},"data-uri":function(b,e){if("undefined"!=typeof a)return new d.URL(e||b,this.currentFileInfo).eval(this.env);var f=b.value,g=e&&e.value,h=c("fs"),i=c("path"),j=!1;if(arguments.length<2&&(g=f),this.env.isPathRelative(g)&&(g=this.currentFileInfo.relativeUrls?i.join(this.currentFileInfo.currentDirectory,g):i.join(this.currentFileInfo.entryPath,g)),arguments.length<2){var k;try{k=c("mime")}catch(l){k=d._mime}f=k.lookup(g);var m=k.charsets.lookup(f);j=["US-ASCII","UTF-8"].indexOf(m)<0,j&&(f+=";base64")}else j=/;base64$/.test(f);var n=h.readFileSync(g),o=32,p=parseInt(n.length/1024,10);if(p>=o&&this.env.ieCompat!==!1)return this.env.silent||console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!",g,p,o),new d.URL(e||b,this.currentFileInfo).eval(this.env);n=j?n.toString("base64"):encodeURIComponent(n);var q='"data:'+f+","+n+'"';return new d.URL(new d.Anonymous(q))},"svg-gradient":function(a){function e(){throw{type:"Argument",message:"svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]"}}arguments.length<3&&e();var f,g,h,i,j,k,l,m=Array.prototype.slice.call(arguments,1),n="linear",o='x="0" y="0" width="1" height="1"',p=!0,q={compress:!1},r=a.toCSS(q);switch(r){case"to bottom":f='x1="0%" y1="0%" x2="0%" y2="100%"';break;case"to right":f='x1="0%" y1="0%" x2="100%" y2="0%"';break;case"to bottom right":f='x1="0%" y1="0%" x2="100%" y2="100%"';break;case"to top right":f='x1="0%" y1="100%" x2="100%" y2="0%"';break;case"ellipse":case"ellipse at center":n="radial",f='cx="50%" cy="50%" r="75%"',o='x="-50" y="-50" width="101" height="101"';break;default:throw{type:"Argument",message:"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"}}for(g='<'+n+'Gradient id="gradient" gradientUnits="userSpaceOnUse" '+f+">",h=0;hl?' stop-opacity="'+l+'"':"")+"/>";if(g+="',p)try{g=c("./encoder").encodeBase64(g)}catch(s){p=!1}return g="'data:image/svg+xml"+(p?";base64":"")+","+g+"'",new d.URL(new d.Anonymous(g))}},d._mime={_types:{".htm":"text/html",".html":"text/html",".gif":"image/gif",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png"},lookup:function(a){var e=c("path").extname(a),f=d._mime._types[e];if(f===b)throw new Error('Optional dependency "mime" is required for '+e);return f},charsets:{lookup:function(a){return a&&/^text\//.test(a)?"UTF-8":""}}};var l={ceil:null,floor:null,sqrt:null,abs:null,tan:"",sin:"",cos:"",atan:"rad",asin:"rad",acos:"rad"},m={multiply:function(a,b){return a*b},screen:function(a,b){return a+b-a*b},overlay:function(a,b){return a*=2,1>=a?m.multiply(a,b):m.screen(a-1,b)},softlight:function(a,b){var c=1,d=a;return b>.5&&(d=1,c=a>.25?Math.sqrt(a):((16*a-12)*a+4)*a),a-(1-2*b)*d*(c-a)},hardlight:function(a,b){return m.overlay(b,a)},difference:function(a,b){return Math.abs(a-b)},exclusion:function(a,b){return a+b-2*a*b},average:function(a,b){return(a+b)/2},negation:function(a,b){return 1-Math.abs(a+b-1)}};d.defaultFunc={eval:function(){var a=this.value_,b=this.error_;if(b)throw b;return null!=a?a?d.True:d.False:void 0},value:function(a){this.value_=a},error:function(a){this.error_=a},reset:function(){this.value_=this.error_=null}},g(),d.fround=function(a,b){var c;return a&&null!=a.numPrecision?(c=Math.pow(10,a.numPrecision),Math.round(b*c)/c):b},d.functionCall=function(a,b){this.env=a,this.currentFileInfo=b},d.functionCall.prototype=d.functions}(c("./tree")),function(a){a.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"}}(c("./tree")),function(a){a.debugInfo=function(b,c,d){var e="";if(b.dumpLineNumbers&&!b.compress)switch(b.dumpLineNumbers){case"comments":e=a.debugInfo.asComment(c);break;case"mediaquery":e=a.debugInfo.asMediaQuery(c);break;case"all":e=a.debugInfo.asComment(c)+(d||"")+a.debugInfo.asMediaQuery(c)}return e},a.debugInfo.asComment=function(a){return"/* line "+a.debugInfo.lineNumber+", "+a.debugInfo.fileName+" */\n"},a.debugInfo.asMediaQuery=function(a){return"@media -sass-debug-info{filename{font-family:"+("file://"+a.debugInfo.fileName).replace(/([.:\/\\])/g,function(a){return"\\"==a&&(a="/"),"\\"+a})+"}line{font-family:\\00003"+a.debugInfo.lineNumber+"}}\n"},a.find=function(a,b){for(var c,d=0;d1?"["+a.value.map(function(a){return a.toCSS(!1)}).join(", ")+"]":a.toCSS(!1)},a.toCSS=function(a){var b=[];return this.genCSS(a,{add:function(a){b.push(a)},isEmpty:function(){return 0===b.length}}),b.join("")},a.outputRuleset=function(a,b,c){var d,e=c.length;if(a.tabLevel=(0|a.tabLevel)+1,a.compress){for(b.add("{"),d=0;e>d;d++)c[d].genCSS(a,b);return b.add("}"),void a.tabLevel--}var f="\n"+Array(a.tabLevel).join(" "),g=f+" ";if(e){for(b.add(" {"+g),c[0].genCSS(a,b),d=1;e>d;d++)b.add(g),c[d].genCSS(a,b);b.add(f+"}")}else b.add(" {"+f+"}");a.tabLevel--}}(c("./tree")),function(a){a.Alpha=function(a){this.value=a},a.Alpha.prototype={type:"Alpha",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Alpha(this.value.eval(b)):this},genCSS:function(a,b){b.add("alpha(opacity="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value),b.add(")")},toCSS:a.toCSS}}(c("../tree")),function(a){a.Anonymous=function(a,b,c,d){this.value=a.value||a,this.index=b,this.mapLines=d,this.currentFileInfo=c},a.Anonymous.prototype={type:"Anonymous",eval:function(){return new a.Anonymous(this.value,this.index,this.currentFileInfo,this.mapLines)},compare:function(a){if(!a.toCSS)return-1;var b=this.toCSS(),c=a.toCSS();return b===c?0:c>b?-1:1},genCSS:function(a,b){b.add(this.value,this.currentFileInfo,this.index,this.mapLines)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Assignment=function(a,b){this.key=a,this.value=b},a.Assignment.prototype={type:"Assignment",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Assignment(this.key,this.value.eval(b)):this},genCSS:function(a,b){b.add(this.key+"="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Call=function(a,b,c,d){this.name=a,this.args=b,this.index=c,this.currentFileInfo=d},a.Call.prototype={type:"Call",accept:function(a){this.args&&(this.args=a.visitArray(this.args))},eval:function(b){var c,d,e=this.args.map(function(a){return a.eval(b)}),f=this.name.toLowerCase();if(f in a.functions)try{if(d=new a.functionCall(b,this.currentFileInfo),c=d[f].apply(d,e),null!=c)return c}catch(g){throw{type:g.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(g.message?": "+g.message:""),index:this.index,filename:this.currentFileInfo.filename}}return new a.Call(this.name,e,this.index,this.currentFileInfo)},genCSS:function(a,b){b.add(this.name+"(",this.currentFileInfo,this.index);for(var c=0;ca?"0":"")+a.toString(16)}).join("")}function c(a,b){return Math.min(Math.max(a,0),b)}a.Color=function(a,b){this.rgb=Array.isArray(a)?a:6==a.length?a.match(/.{2}/g).map(function(a){return parseInt(a,16)}):a.split("").map(function(a){return parseInt(a+a,16)}),this.alpha="number"==typeof b?b:1};var d="transparent";a.Color.prototype={type:"Color",eval:function(){return this},luma:function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255;return a=.03928>=a?a/12.92:Math.pow((a+.055)/1.055,2.4),b=.03928>=b?b/12.92:Math.pow((b+.055)/1.055,2.4),c=.03928>=c?c/12.92:Math.pow((c+.055)/1.055,2.4),.2126*a+.7152*b+.0722*c},genCSS:function(a,b){b.add(this.toCSS(a))},toCSS:function(b,e){var f=b&&b.compress&&!e,g=a.fround(b,this.alpha);if(1>g)return 0===g&&this.isTransparentKeyword?d:"rgba("+this.rgb.map(function(a){return c(Math.round(a),255)}).concat(c(g,1)).join(","+(f?"":" "))+")";var h=this.toRGB();if(f){var i=h.split("");i[1]===i[2]&&i[3]===i[4]&&i[5]===i[6]&&(h="#"+i[1]+i[3]+i[5])}return h},operate:function(b,c,d){for(var e=[],f=this.alpha*(1-d.alpha)+d.alpha,g=0;3>g;g++)e[g]=a.operate(b,c,this.rgb[g],d.rgb[g]);return new a.Color(e,f)},toRGB:function(){return b(this.rgb)},toHSL:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=(g+h)/2,j=g-h;if(g===h)a=b=0;else{switch(b=i>.5?j/(2-g-h):j/(g+h),g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,l:i,a:f}},toHSV:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=g,j=g-h;if(b=0===g?0:j/g,g===h)a=0;else{switch(g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,v:i,a:f}},toARGB:function(){return b([255*this.alpha].concat(this.rgb))},compare:function(a){return a.rgb?a.rgb[0]===this.rgb[0]&&a.rgb[1]===this.rgb[1]&&a.rgb[2]===this.rgb[2]&&a.alpha===this.alpha?0:-1:-1}},a.Color.fromKeyword=function(b){if(b=b.toLowerCase(),a.colors.hasOwnProperty(b))return new a.Color(a.colors[b].slice(1));if(b===d){var c=new a.Color([0,0,0],0);return c.isTransparentKeyword=!0,c}}}(c("../tree")),function(a){a.Comment=function(a,b,c,d){this.value=a,this.silent=!!b,this.currentFileInfo=d},a.Comment.prototype={type:"Comment",genCSS:function(b,c){this.debugInfo&&c.add(a.debugInfo(b,this),this.currentFileInfo,this.index),c.add(this.value.trim())},toCSS:a.toCSS,isSilent:function(a){var b=this.currentFileInfo&&this.currentFileInfo.reference&&!this.isReferenced,c=a.compress&&!this.value.match(/^\/\*!/);return this.silent||b||c},eval:function(){return this},markReferenced:function(){this.isReferenced=!0}}}(c("../tree")),function(a){a.Condition=function(a,b,c,d,e){this.op=a.trim(),this.lvalue=b,this.rvalue=c,this.index=d,this.negate=e},a.Condition.prototype={type:"Condition",accept:function(a){this.lvalue=a.visit(this.lvalue),this.rvalue=a.visit(this.rvalue)},eval:function(a){var b,c=this.lvalue.eval(a),d=this.rvalue.eval(a),e=this.index;return b=function(a){switch(a){case"and":return c&&d;case"or":return c||d;default:if(c.compare)b=c.compare(d);else{if(!d.compare)throw{type:"Type",message:"Unable to perform comparison",index:e};b=d.compare(c)}switch(b){case-1:return"<"===a||"=<"===a||"<="===a;case 0:return"="===a||">="===a||"=<"===a||"<="===a;case 1:return">"===a||">="===a}}}(this.op),this.negate?!b:b}}}(c("../tree")),function(a){a.DetachedRuleset=function(a,b){this.ruleset=a,this.frames=b},a.DetachedRuleset.prototype={type:"DetachedRuleset",accept:function(a){this.ruleset=a.visit(this.ruleset)},eval:function(b){var c=this.frames||b.frames.slice(0);return new a.DetachedRuleset(this.ruleset,c)},callEval:function(b){return this.ruleset.eval(this.frames?new a.evalEnv(b,this.frames.concat(b.frames)):b)}}}(c("../tree")),function(a){a.Dimension=function(c,d){this.value=parseFloat(c),this.unit=d&&d instanceof a.Unit?d:new a.Unit(d?[d]:b)},a.Dimension.prototype={type:"Dimension",accept:function(a){this.unit=a.visit(this.unit)},eval:function(){return this},toColor:function(){return new a.Color([this.value,this.value,this.value])},genCSS:function(b,c){if(b&&b.strictUnits&&!this.unit.isSingular())throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());var d=a.fround(b,this.value),e=String(d);if(0!==d&&1e-6>d&&d>-1e-6&&(e=d.toFixed(20).replace(/0+$/,"")),b&&b.compress){if(0===d&&this.unit.isLength())return void c.add(e);d>0&&1>d&&(e=e.substr(1))}c.add(e),this.unit.genCSS(b,c)},toCSS:a.toCSS,operate:function(b,c,d){var e=a.operate(b,c,this.value,d.value),f=this.unit.clone();if("+"===c||"-"===c)if(0===f.numerator.length&&0===f.denominator.length)f.numerator=d.unit.numerator.slice(0),f.denominator=d.unit.denominator.slice(0);else if(0===d.unit.numerator.length&&0===f.denominator.length);else{if(d=d.convertTo(this.unit.usedUnits()),b.strictUnits&&d.unit.toString()!==f.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+f.toString()+"' and '"+d.unit.toString()+"'.");e=a.operate(b,c,this.value,d.value)}else"*"===c?(f.numerator=f.numerator.concat(d.unit.numerator).sort(),f.denominator=f.denominator.concat(d.unit.denominator).sort(),f.cancel()):"/"===c&&(f.numerator=f.numerator.concat(d.unit.denominator).sort(),f.denominator=f.denominator.concat(d.unit.numerator).sort(),f.cancel());return new a.Dimension(e,f)},compare:function(b){if(b instanceof a.Dimension){var c,d,e,f;if(this.unit.isEmpty()||b.unit.isEmpty())c=this,d=b;else if(c=this.unify(),d=b.unify(),0!==c.unit.compare(d.unit))return-1;return e=c.value,f=d.value,f>e?-1:e>f?1:0}return-1},unify:function(){return this.convertTo({length:"px",duration:"s",angle:"rad"})},convertTo:function(b){var c,d,e,f,g,h=this.value,i=this.unit.clone(),j={};if("string"==typeof b){for(c in a.UnitConversions)a.UnitConversions[c].hasOwnProperty(b)&&(j={},j[c]=b);b=j}g=function(a,b){return e.hasOwnProperty(a)?(b?h/=e[a]/e[f]:h*=e[a]/e[f],f):a};for(d in b)b.hasOwnProperty(d)&&(f=b[d],e=a.UnitConversions[d],i.map(g));return i.cancel(),new a.Dimension(h,i)}},a.UnitConversions={length:{m:1,cm:.01,mm:.001,"in":.0254,px:.0254/96,pt:.0254/72,pc:.0254/72*12},duration:{s:1,ms:.001},angle:{rad:1/(2*Math.PI),deg:1/360,grad:.0025,turn:1}},a.Unit=function(a,b,c){this.numerator=a?a.slice(0).sort():[],this.denominator=b?b.slice(0).sort():[],this.backupUnit=c},a.Unit.prototype={type:"Unit",clone:function(){return new a.Unit(this.numerator.slice(0),this.denominator.slice(0),this.backupUnit)},genCSS:function(a,b){this.numerator.length>=1?b.add(this.numerator[0]):this.denominator.length>=1?b.add(this.denominator[0]):a&&a.strictUnits||!this.backupUnit||b.add(this.backupUnit)},toCSS:a.toCSS,toString:function(){var a,b=this.numerator.join("*");for(a=0;a0)for(b=0;e>b;b++)this.numerator.push(a);else if(0>e)for(b=0;-e>b;b++)this.denominator.push(a)}0===this.numerator.length&&0===this.denominator.length&&c&&(this.backupUnit=c),this.numerator.sort(),this.denominator.sort()}}}(c("../tree")),function(a){a.Directive=function(a,b,c,d,e,f){this.name=a,this.value=b,c&&(this.rules=c,this.rules.allowImports=!0),this.index=d,this.currentFileInfo=e,this.debugInfo=f},a.Directive.prototype={type:"Directive",accept:function(a){var b=this.value,c=this.rules;c&&(c=a.visit(c)),b&&(b=a.visit(b))},genCSS:function(b,c){var d=this.value,e=this.rules;c.add(this.name,this.currentFileInfo,this.index),d&&(c.add(" "),d.genCSS(b,c)),e?a.outputRuleset(b,c,[e]):c.add(";")},toCSS:a.toCSS,eval:function(b){var c=this.value,d=this.rules;return c&&(c=c.eval(b)),d&&(d=d.eval(b),d.root=!0),new a.Directive(this.name,c,d,this.index,this.currentFileInfo,this.debugInfo)},variable:function(b){return this.rules?a.Ruleset.prototype.variable.call(this.rules,b):void 0},find:function(){return this.rules?a.Ruleset.prototype.find.apply(this.rules,arguments):void 0},rulesets:function(){return this.rules?a.Ruleset.prototype.rulesets.apply(this.rules):void 0},markReferenced:function(){var a,b;if(this.isReferenced=!0,this.rules)for(b=this.rules.rules,a=0;a":" > ","|":"|","^":" ^ ","^^":" ^^ "},_outputMapCompressed:{"":""," ":" ",":":" :","+":"+","~":"~",">":">","|":"|","^":"^","^^":"^^"},genCSS:function(a,b){b.add((a.compress?this._outputMapCompressed:this._outputMap)[this.value])},toCSS:a.toCSS}}(c("../tree")),function(a){a.Expression=function(a){this.value=a},a.Expression.prototype={type:"Expression",accept:function(a){this.value&&(this.value=a.visitArray(this.value))},eval:function(b){var c,d=this.parens&&!this.parensInOp,e=!1;return d&&b.inParenthesis(),this.value.length>1?c=new a.Expression(this.value.map(function(a){return a.eval(b)})):1===this.value.length?(this.value[0].parens&&!this.value[0].parensInOp&&(e=!0),c=this.value[0].eval(b)):c=this,d&&b.outOfParenthesis(),this.parens&&this.parensInOp&&!b.isMathOn()&&!e&&(c=new a.Paren(c)),c},genCSS:function(a,b){for(var c=0;c0&&c.length&&""===c[0].combinator.value&&(c[0].combinator.value=" "),d=d.concat(a[b].elements);this.selfSelectors=[{elements:d}]}}}(c("../tree")),function(a){a.Import=function(a,c,d,e,f){if(this.options=d,this.index=e,this.path=a,this.features=c,this.currentFileInfo=f,this.options.less!==b||this.options.inline)this.css=!this.options.less||this.options.inline;else{var g=this.getPath();g&&/css([\?;].*)?$/.test(g)&&(this.css=!0)}},a.Import.prototype={type:"Import",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.path=a.visit(this.path),!this.options.inline&&this.root&&(this.root=a.visit(this.root))},genCSS:function(a,b){this.css&&(b.add("@import ",this.currentFileInfo,this.index),this.path.genCSS(a,b),this.features&&(b.add(" "),this.features.genCSS(a,b)),b.add(";"))},toCSS:a.toCSS,getPath:function(){if(this.path instanceof a.Quoted){var c=this.path.value;return this.css!==b||/(\.[a-z]*$)|([\?;].*)$/.test(c)?c:c+".less"}return this.path instanceof a.URL?this.path.value.value:null},evalForImport:function(b){return new a.Import(this.path.eval(b),this.features,this.options,this.index,this.currentFileInfo)},evalPath:function(b){var c=this.path.eval(b),d=this.currentFileInfo&&this.currentFileInfo.rootpath;if(!(c instanceof a.URL)){if(d){var e=c.value;e&&b.isPathRelative(e)&&(c.value=d+e)}c.value=b.normalizePath(c.value)}return c},eval:function(b){var c,d=this.features&&this.features.eval(b);if(this.skip&&("function"==typeof this.skip&&(this.skip=this.skip()),this.skip))return[];if(this.options.inline){var e=new a.Anonymous(this.root,0,{filename:this.importedFilename},!0);return this.features?new a.Media([e],this.features.value):[e]}if(this.css){var f=new a.Import(this.evalPath(b),d,this.options,this.index);if(!f.css&&this.error)throw this.error;return f}return c=new a.Ruleset(null,this.root.rules.slice(0)),c.evalImports(b),this.features?new a.Media(c.rules,this.features.value):c.rules}}}(c("../tree")),function(a){a.JavaScript=function(a,b,c){this.escaped=c,this.expression=a,this.index=b},a.JavaScript.prototype={type:"JavaScript",eval:function(b){var c,d=this,e={},f=this.expression.replace(/@\{([\w-]+)\}/g,function(c,e){return a.jsify(new a.Variable("@"+e,d.index).eval(b))});try{f=new Function("return ("+f+")")}catch(g){throw{message:"JavaScript evaluation error: "+g.message+" from `"+f+"`",index:this.index}}var h=b.frames[0].variables();for(var i in h)h.hasOwnProperty(i)&&(e[i.slice(1)]={value:h[i].value,toJS:function(){return this.value.eval(b).toCSS()}});try{c=f.call(e)}catch(g){throw{message:"JavaScript evaluation error: '"+g.name+": "+g.message.replace(/["]/g,"'")+"'",index:this.index}}return"number"==typeof c?new a.Dimension(c):"string"==typeof c?new a.Quoted('"'+c+'"',c,this.escaped,this.index):new a.Anonymous(Array.isArray(c)?c.join(", "):c)}}}(c("../tree")),function(a){a.Keyword=function(a){this.value=a},a.Keyword.prototype={type:"Keyword",eval:function(){return this},genCSS:function(a,b){if("%"===this.value)throw{type:"Syntax",message:"Invalid % without number"};b.add(this.value)},toCSS:a.toCSS,compare:function(b){return b instanceof a.Keyword?b.value===this.value?0:1:-1}},a.True=new a.Keyword("true"),a.False=new a.Keyword("false")}(c("../tree")),function(a){a.Media=function(b,c,d,e){this.index=d,this.currentFileInfo=e;var f=this.emptySelectors();this.features=new a.Value(c),this.rules=[new a.Ruleset(f,b)],this.rules[0].allowImports=!0},a.Media.prototype={type:"Media",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.rules&&(this.rules=a.visitArray(this.rules))},genCSS:function(b,c){c.add("@media ",this.currentFileInfo,this.index),this.features.genCSS(b,c),a.outputRuleset(b,c,this.rules)},toCSS:a.toCSS,eval:function(b){b.mediaBlocks||(b.mediaBlocks=[],b.mediaPath=[]);var c=new a.Media(null,[],this.index,this.currentFileInfo);this.debugInfo&&(this.rules[0].debugInfo=this.debugInfo,c.debugInfo=this.debugInfo);var d=!1;b.strictMath||(d=!0,b.strictMath=!0);try{c.features=this.features.eval(b)}finally{d&&(b.strictMath=!1)}return b.mediaPath.push(c),b.mediaBlocks.push(c),b.frames.unshift(this.rules[0]),c.rules=[this.rules[0].eval(b)],b.frames.shift(),b.mediaPath.pop(),0===b.mediaPath.length?c.evalTop(b):c.evalNested(b)},variable:function(b){return a.Ruleset.prototype.variable.call(this.rules[0],b)},find:function(){return a.Ruleset.prototype.find.apply(this.rules[0],arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.rules[0])},emptySelectors:function(){var b=new a.Element("","&",this.index,this.currentFileInfo),c=[new a.Selector([b],null,null,this.index,this.currentFileInfo)];return c[0].mediaEmpty=!0,c},markReferenced:function(){var a,b=this.rules[0].rules;for(this.rules[0].markReferenced(),this.isReferenced=!0,a=0;a1){var d=this.emptySelectors();c=new a.Ruleset(d,b.mediaBlocks),c.multiMedia=!0}return delete b.mediaBlocks,delete b.mediaPath,c},evalNested:function(b){var c,d,e=b.mediaPath.concat([this]);for(c=0;c0;c--)b.splice(c,0,new a.Anonymous("and"));return new a.Expression(b)})),new a.Ruleset([],[])},permute:function(a){if(0===a.length)return[];if(1===a.length)return a[0];for(var b=[],c=this.permute(a.slice(1)),d=0;d0){for(j=!0,g=0;gh;h++)s.value(h),r[h]=d.matchCondition(e,b);(r[0]||r[1])&&(r[0]!=r[1]&&(l.group=r[1]?u:v),q.push(l))}else q.push(l);p=!0}}for(s.reset(),n=[0,0,0],g=0;g0)m=v;else if(m=u,n[u]+n[v]>1)throw{type:"Runtime",message:"Ambiguous use of `default()` found when matching for `"+this.format(e)+"`",index:this.index,filename:this.currentFileInfo.filename};for(g=0;gh;h++)if(g=d[h],k=g&&g.name){for(l=!1,i=0;ii;i++)f.push(d[i].value.eval(b));n.prependRule(new a.Rule(k,new a.Expression(f).eval(b)))}else{if(j=g&&g.value)j=j.eval(b);else{if(!o[h].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+p+" for "+this.arity+")"};j=o[h].value.eval(c),n.resetCache()}n.prependRule(new a.Rule(k,j)),e[h]=j}if(o[h].variadic&&d)for(i=m;p>i;i++)e[i]=d[i].value.eval(b);m++}return n},eval:function(b){return new a.mixin.Definition(this.name,this.params,this.rules,this.condition,this.variadic,this.frames||b.frames.slice(0))},evalCall:function(b,c,d){var e,f,g=[],h=this.frames?this.frames.concat(b.frames):b.frames,i=this.evalParams(b,new a.evalEnv(b,h),c,g);return i.prependRule(new a.Rule("@arguments",new a.Expression(g).eval(b))),e=this.rules.slice(0),f=new a.Ruleset(null,e),f.originalRuleset=this,f=f.eval(new a.evalEnv(b,[this,i].concat(h))),d&&(f=this.parent.makeImportant.apply(f)),f},matchCondition:function(b,c){return this.condition&&!this.condition.eval(new a.evalEnv(c,[this.evalParams(c,new a.evalEnv(c,this.frames.concat(c.frames)),b,[])].concat(this.frames).concat(c.frames)))?!1:!0},matchArgs:function(a,b){var c,d=a&&a.length||0;if(this.variadic){if(dthis.params.length)return!1}c=Math.min(d,this.arity);for(var e=0;c>e;e++)if(!this.params[e].name&&!this.params[e].variadic&&a[e].value.eval(b).toCSS()!=this.params[e].value.eval(b).toCSS())return!1;return!0}}}(c("../tree")),function(a){a.Negative=function(a){this.value=a},a.Negative.prototype={type:"Negative",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("-"),this.value.genCSS(a,b)},toCSS:a.toCSS,eval:function(b){return b.isMathOn()?new a.Operation("*",[new a.Dimension(-1),this.value]).eval(b):new a.Negative(this.value.eval(b))}}}(c("../tree")),function(a){a.Operation=function(a,b,c){this.op=a.trim(),this.operands=b,this.isSpaced=c},a.Operation.prototype={type:"Operation",accept:function(a){this.operands=a.visit(this.operands)},eval:function(b){var c=this.operands[0].eval(b),d=this.operands[1].eval(b);if(b.isMathOn()){if(c instanceof a.Dimension&&d instanceof a.Color&&(c=c.toColor()),d instanceof a.Dimension&&c instanceof a.Color&&(d=d.toColor()),!c.operate)throw{type:"Operation",message:"Operation on an invalid type"};return c.operate(b,this.op,d)}return new a.Operation(this.op,[c,d],this.isSpaced)},genCSS:function(a,b){this.operands[0].genCSS(a,b),this.isSpaced&&b.add(" "),b.add(this.op),this.isSpaced&&b.add(" "),this.operands[1].genCSS(a,b)},toCSS:a.toCSS},a.operate=function(a,b,c,d){switch(b){case"+":return c+d;case"-":return c-d;case"*":return c*d;case"/":return c/d}}}(c("../tree")),function(a){a.Paren=function(a){this.value=a},a.Paren.prototype={type:"Paren",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("("),this.value.genCSS(a,b),b.add(")")},toCSS:a.toCSS,eval:function(b){return new a.Paren(this.value.eval(b))}}}(c("../tree")),function(a){a.Quoted=function(a,b,c,d,e){this.escaped=c,this.value=b||"",this.quote=a.charAt(0),this.index=d,this.currentFileInfo=e},a.Quoted.prototype={type:"Quoted",genCSS:function(a,b){this.escaped||b.add(this.quote,this.currentFileInfo,this.index),b.add(this.value),this.escaped||b.add(this.quote)},toCSS:a.toCSS,eval:function(b){var c=this,d=this.value.replace(/`([^`]+)`/g,function(d,e){return new a.JavaScript(e,c.index,!0).eval(b).value}).replace(/@\{([\w-]+)\}/g,function(d,e){var f=new a.Variable("@"+e,c.index,c.currentFileInfo).eval(b,!0);return f instanceof a.Quoted?f.value:f.toCSS()});return new a.Quoted(this.quote+d+this.quote,d,this.escaped,this.index,this.currentFileInfo)},compare:function(a){if(!a.toCSS)return-1;var b=this.toCSS(),c=a.toCSS();return b===c?0:c>b?-1:1}}}(c("../tree")),function(a){function b(a,b){var c,d="",e=b.length,f={add:function(a){d+=a}};for(c=0;e>c;c++)b[c].eval(a).genCSS(a,f);return d}a.Rule=function(b,c,d,e,f,g,h){this.name=b,this.value=c instanceof a.Value||c instanceof a.Ruleset?c:new a.Value([c]),this.important=d?" "+d.trim():"",this.merge=e,this.index=f,this.currentFileInfo=g,this.inline=h||!1,this.variable=b.charAt&&"@"===b.charAt(0)},a.Rule.prototype={type:"Rule",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add(this.name+(a.compress?":":": "),this.currentFileInfo,this.index);try{this.value.genCSS(a,b)}catch(c){throw c.index=this.index,c.filename=this.currentFileInfo.filename,c}b.add(this.important+(this.inline||a.lastRule&&a.compress?"":";"),this.currentFileInfo,this.index)},toCSS:a.toCSS,eval:function(c){var d,e=!1,f=this.name;"string"!=typeof f&&(f=1===f.length&&f[0]instanceof a.Keyword?f[0].value:b(c,f)),"font"!==f||c.strictMath||(e=!0,c.strictMath=!0);try{if(d=this.value.eval(c),!this.variable&&"DetachedRuleset"===d.type)throw{message:"Rulesets cannot be evaluated on a property.",index:this.index,filename:this.currentFileInfo.filename};return new a.Rule(f,d,this.important,this.merge,this.index,this.currentFileInfo,this.inline)}catch(g){throw"number"!=typeof g.index&&(g.index=this.index,g.filename=this.currentFileInfo.filename),g}finally{e&&(c.strictMath=!1)}},makeImportant:function(){return new a.Rule(this.name,this.value,"!important",this.merge,this.index,this.currentFileInfo,this.inline)}}}(c("../tree")),function(a){a.RulesetCall=function(a){this.variable=a},a.RulesetCall.prototype={type:"RulesetCall",accept:function(){},eval:function(b){var c=new a.Variable(this.variable).eval(b);return c.callEval(b)}}}(c("../tree")),function(a){a.Ruleset=function(a,b,c){this.selectors=a,this.rules=b,this._lookups={},this.strictImports=c},a.Ruleset.prototype={type:"Ruleset",accept:function(a){this.paths?a.visitArray(this.paths,!0):this.selectors&&(this.selectors=a.visitArray(this.selectors)),this.rules&&this.rules.length&&(this.rules=a.visitArray(this.rules))},eval:function(b){var c,d,e,f,g=this.selectors,h=a.defaultFunc,i=!1;if(g&&(d=g.length)){for(c=[],h.error({type:"Syntax",message:"it is currently only allowed in parametric mixin guards,"}),f=0;d>f;f++)e=g[f].eval(b),c.push(e),e.evaldCondition&&(i=!0);h.reset()}else i=!0;var j,k,l=this.rules?this.rules.slice(0):null,m=new a.Ruleset(c,l,this.strictImports);m.originalRuleset=this,m.root=this.root,m.firstRoot=this.firstRoot,m.allowImports=this.allowImports,this.debugInfo&&(m.debugInfo=this.debugInfo),i||(l.length=0);var n=b.frames;n.unshift(m);var o=b.selectors;o||(b.selectors=o=[]),o.unshift(this.selectors),(m.root||m.allowImports||!m.strictImports)&&m.evalImports(b);var p=m.rules,q=p?p.length:0;for(f=0;q>f;f++)(p[f]instanceof a.mixin.Definition||p[f]instanceof a.DetachedRuleset)&&(p[f]=p[f].eval(b));var r=b.mediaBlocks&&b.mediaBlocks.length||0;for(f=0;q>f;f++)p[f]instanceof a.mixin.Call?(l=p[f].eval(b).filter(function(b){return b instanceof a.Rule&&b.variable?!m.variable(b.name):!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache()):p[f]instanceof a.RulesetCall&&(l=p[f].eval(b).rules.filter(function(b){return b instanceof a.Rule&&b.variable?!1:!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache());for(f=0;fb;b++)c=g[b],(c instanceof d||c instanceof e)&&f.push(c);return f},prependRule:function(a){var b=this.rules;b?b.unshift(a):this.rules=[a]},find:function(b,c){c=c||this;var d,e=[],f=b.toCSS();return f in this._lookups?this._lookups[f]:(this.rulesets().forEach(function(f){if(f!==c)for(var g=0;gd?Array.prototype.push.apply(e,f.find(new a.Selector(b.elements.slice(d)),c)):e.push(f);break}}),this._lookups[f]=e,e)},genCSS:function(b,c){var d,e,f,g,h,i,j=[],k=[];b.tabLevel=b.tabLevel||0,this.root||b.tabLevel++;var l,m=b.compress?"":Array(b.tabLevel+1).join(" "),n=b.compress?"":Array(b.tabLevel).join(" ");for(d=0;dd;d++)if(i=p[d],o=i.length)for(d>0&&c.add(l),b.firstSelector=!0,i[0].genCSS(b,c),b.firstSelector=!1,e=1;o>e;e++)i[e].genCSS(b,c);c.add((b.compress?"{":" {\n")+m)}for(d=0;dd;d++)l&&c.add(l),k[d].genCSS(b,c);c.isEmpty()||b.compress||!this.firstRoot||c.add("\n")},toCSS:a.toCSS,markReferenced:function(){if(this.selectors)for(var a=0;a0&&this.mergeElementsOnToSelectors(r,i),f=0;f0&&(k[0].elements=k[0].elements.slice(0),k[0].elements.push(new a.Element(j.combinator,"",j.index,j.currentFileInfo))),s.push(k);else for(g=0;g0?(m=k.slice(0),q=m.pop(),o=d.createDerived(q.elements.slice(0)),p=!1):o=d.createDerived([]),l.length>1&&(n=n.concat(l.slice(1))),l.length>0&&(p=!1,o.elements.push(new a.Element(j.combinator,l[0].elements[0].value,j.index,j.currentFileInfo)),o.elements=o.elements.concat(l[0].elements.slice(1))),p||m.push(o),m=m.concat(n),s.push(m);i=s,r=[]}for(r.length>0&&this.mergeElementsOnToSelectors(r,i),e=0;e0&&b.push(i[e])}else if(c.length>0)for(e=0;e0?e[e.length-1]=e[e.length-1].createDerived(e[e.length-1].elements.concat(b)):e.push(new a.Selector(b))}}}(c("../tree")),function(a){a.Selector=function(a,b,c,d,e,f){this.elements=a,this.extendList=b,this.condition=c,this.currentFileInfo=e||{},this.isReferenced=f,c||(this.evaldCondition=!0)},a.Selector.prototype={type:"Selector",accept:function(a){this.elements&&(this.elements=a.visitArray(this.elements)),this.extendList&&(this.extendList=a.visitArray(this.extendList)),this.condition&&(this.condition=a.visit(this.condition))},createDerived:function(b,c,d){d=null!=d?d:this.evaldCondition;var e=new a.Selector(b,c||this.extendList,null,this.index,this.currentFileInfo,this.isReferenced);return e.evaldCondition=d,e.mediaEmpty=this.mediaEmpty,e},match:function(a){var b,c,d=this.elements,e=d.length;if(a.CacheElements(),b=a._elements.length,0===b||b>e)return 0;for(c=0;b>c;c++)if(d[c].value!==a._elements[c])return 0;return b},CacheElements:function(){var a,b,c,d="";if(!this._elements){for(a=this.elements.length,c=0;a>c;c++)if(b=this.elements[c],d+=b.combinator.value,b.value.value){if("string"!=typeof b.value.value){d="";break}d+=b.value.value}else d+=b.value;this._elements=d.match(/[,&#\.\w-]([\w-]|(\\.))*/g),this._elements?"&"===this._elements[0]&&this._elements.shift():this._elements=[]}},isJustParentSelector:function(){return!this.mediaEmpty&&1===this.elements.length&&"&"===this.elements[0].value&&(" "===this.elements[0].combinator.value||""===this.elements[0].combinator.value)},eval:function(a){var b=this.condition&&this.condition.eval(a),c=this.elements,d=this.extendList;return c=c&&c.map(function(b){return b.eval(a)}),d=d&&d.map(function(b){return b.eval(a)}),this.createDerived(c,d,b)},genCSS:function(a,b){var c,d;if(a&&a.firstSelector||""!==this.elements[0].combinator.value||b.add(" ",this.currentFileInfo,this.index),!this._css)for(c=0;cc;c++)this.visit(a[c]);return a}var e=[];for(c=0;d>c;c++){var f=this.visit(a[c]);f.splice?f.length&&this.flatten(f,e):e.push(f)}return e},flatten:function(a,b){b||(b=[]);var c,d,e,f,g,h;for(d=0,c=a.length;c>d;d++)if(e=a[d],e.splice)for(g=0,f=e.length;f>g;g++)h=e[g],h.splice?h.length&&this.flatten(h,b):b.push(h);else b.push(e);return b}}}(c("./tree")),function(a){a.importVisitor=function(b,c,d,e,f){if(this._visitor=new a.visitor(this),this._importer=b,this._finish=c,this.env=d||new a.evalEnv,this.importCount=0,this.onceFileDetectionMap=e||{},this.recursionDetector={},f)for(var g in f)f.hasOwnProperty(g)&&(this.recursionDetector[g]=!0)},a.importVisitor.prototype={isReplacing:!0,run:function(a){var b;try{this._visitor.visit(a)}catch(c){b=c}this.isFinished=!0,0===this.importCount&&this._finish(b)},visitImport:function(b,c){var d,e=this,f=b.options.inline;if(!b.css||f){try{d=b.evalForImport(this.env)}catch(g){g.filename||(g.index=b.index,g.filename=b.currentFileInfo.filename),b.css=!0,b.error=g}if(d&&(!d.css||f)){b=d,this.importCount++;var h=new a.evalEnv(this.env,this.env.frames.slice(0));b.options.multiple&&(h.importMultiple=!0),this._importer.push(b.getPath(),b.currentFileInfo,b.options,function(c,d,g,i){c&&!c.filename&&(c.index=b.index,c.filename=b.currentFileInfo.filename),h.importMultiple||(b.skip=g?!0:function(){return i in e.onceFileDetectionMap?!0:(e.onceFileDetectionMap[i]=!0,!1)});var j=function(a){e.importCount--,0===e.importCount&&e.isFinished&&e._finish(a)};if(d){b.root=d,b.importedFilename=i;var k=g||i in e.recursionDetector;if(!f&&(h.importMultiple||!k))return e.recursionDetector[i]=!0,void new a.importVisitor(e._importer,j,h,e.onceFileDetectionMap,e.recursionDetector).run(d)}j()})}}return c.visitDeeper=!1,b},visitRule:function(a,b){return b.visitDeeper=!1,a},visitDirective:function(a){return this.env.frames.unshift(a),a},visitDirectiveOut:function(){this.env.frames.shift()},visitMixinDefinition:function(a){return this.env.frames.unshift(a),a},visitMixinDefinitionOut:function(){this.env.frames.shift()},visitRuleset:function(a){return this.env.frames.unshift(a),a},visitRulesetOut:function(){this.env.frames.shift()},visitMedia:function(a){return this.env.frames.unshift(a.ruleset),a},visitMediaOut:function(){this.env.frames.shift()}}}(c("./tree")),function(a){a.joinSelectorVisitor=function(){this.contexts=[[]],this._visitor=new a.visitor(this)},a.joinSelectorVisitor.prototype={run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){var b,c=this.contexts[this.contexts.length-1],d=[];this.contexts.push(d),a.root||(b=a.selectors,b&&(b=b.filter(function(a){return a.getIsOutput()}),a.selectors=b.length?b:b=null,b&&a.joinSelectors(d,c,b)),b||(a.rules=null),a.paths=d)},visitRulesetOut:function(){this.contexts.length=this.contexts.length-1},visitMedia:function(a){var b=this.contexts[this.contexts.length-1];a.rules[0].root=0===b.length||b[0].multiMedia}}}(c("./tree")),function(a){a.toCSSVisitor=function(b){this._visitor=new a.visitor(this),this._env=b},a.toCSSVisitor.prototype={isReplacing:!0,run:function(a){return this._visitor.visit(a)},visitRule:function(a){return a.variable?[]:a},visitMixinDefinition:function(a){return a.frames=[],[]},visitExtend:function(){return[]},visitComment:function(a){return a.isSilent(this._env)?[]:a},visitMedia:function(a,b){return a.accept(this._visitor),b.visitDeeper=!1,a.rules.length?a:[]},visitDirective:function(b){if(b.currentFileInfo.reference&&!b.isReferenced)return[];if("@charset"===b.name){if(this.charset){if(b.debugInfo){var c=new a.Comment("/* "+b.toCSS(this._env).replace(/\n/g,"")+" */\n");return c.debugInfo=b.debugInfo,this._visitor.visit(c)}return[]}this.charset=!0}return b},checkPropertiesInRoot:function(b){for(var c,d=0;d0)&&e.splice(0,0,b);else{b.paths&&(b.paths=b.paths.filter(function(b){var c;for(" "===b[0].elements[0].combinator.value&&(b[0].elements[0].combinator=new a.Combinator("")),c=0;ch;)d=f[h],d&&d.rules?(e.push(this._visitor.visit(d)),f.splice(h,1),g--):h++;g>0?b.accept(this._visitor):b.rules=null,c.visitDeeper=!1,f=b.rules,f&&(this._mergeRules(f),f=b.rules),f&&(this._removeDuplicateRules(f),f=b.rules),f&&f.length>0&&b.paths.length>0&&e.splice(0,0,b)}return 1===e.length?e[0]:e},_removeDuplicateRules:function(b){if(b){var c,d,e,f={};for(e=b.length-1;e>=0;e--)if(d=b[e],d instanceof a.Rule)if(f[d.name]){c=f[d.name],c instanceof a.Rule&&(c=f[d.name]=[f[d.name].toCSS(this._env)]);var g=d.toCSS(this._env);-1!==c.indexOf(g)?b.splice(e,1):c.push(g)}else f[d.name]=d}},_mergeRules:function(b){if(b){for(var c,d,e,f={},g=0;g1){d=c[0];var h=[],i=[];c.map(function(a){"+"===a.merge&&(i.length>0&&h.push(e(i)),i=[]),i.push(a)}),h.push(e(i)),d.value=g(h)}})}}}}(c("./tree")),function(a){a.extendFinderVisitor=function(){this._visitor=new a.visitor(this),this.contexts=[],this.allExtendsStack=[[]]},a.extendFinderVisitor.prototype={run:function(a){return a=this._visitor.visit(a),a.allExtends=this.allExtendsStack[0],a},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(b){if(!b.root){var c,d,e,f,g=[],h=b.rules,i=h?h.length:0;for(c=0;i>c;c++)b.rules[c]instanceof a.Extend&&(g.push(h[c]),b.extendOnEveryPath=!0);var j=b.paths;for(c=0;c=0||(i=[k.selfSelectors[0]],g=n.findMatch(j,i),g.length&&j.selfSelectors.forEach(function(b){h=n.extendSelector(g,i,b),l=new a.Extend(k.selector,k.option,0),l.selfSelectors=h,h[h.length-1].extendList=[l],m.push(l),l.ruleset=k.ruleset,l.parent_ids=l.parent_ids.concat(k.parent_ids,j.parent_ids),k.firstExtendOnThisSelectorPath&&(l.firstExtendOnThisSelectorPath=!0,k.ruleset.paths.push(h))}));if(m.length){if(this.extendChainCount++,d>100){var o="{unable to calculate}",p="{unable to calculate}";try{o=m[0].selfSelectors[0].toCSS(),p=m[0].selector.toCSS()}catch(q){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+o+":extend("+p+")"}}return m.concat(n.doExtendChaining(m,c,d+1))}return m},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitSelector:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){if(!a.root){var b,c,d,e,f=this.allExtendsStack[this.allExtendsStack.length-1],g=[],h=this;for(d=0;d0&&k[i.matched].combinator.value!==g?i=null:i.matched++,i&&(i.finished=i.matched===k.length,i.finished&&!a.allowAfter&&(e+1j&&k>0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),k=0,j++),i=f.elements.slice(k,h.index).concat([g]).concat(d.elements.slice(1)),j===h.pathIndex&&e>0?l[l.length-1].elements=l[l.length-1].elements.concat(i):(l=l.concat(c.slice(j,h.pathIndex)),l.push(new a.Selector(i))),j=h.endPathIndex,k=h.endPathElementIndex,k>=c[j].elements.length&&(k=0,j++); -return j0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),j++),l=l.concat(c.slice(j,c.length))},visitRulesetOut:function(){},visitMedia:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitMediaOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1},visitDirective:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitDirectiveOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1}}}(c("./tree")),function(a){a.sourceMapOutput=function(a){this._css=[],this._rootNode=a.rootNode,this._writeSourceMap=a.writeSourceMap,this._contentsMap=a.contentsMap,this._contentsIgnoredCharsMap=a.contentsIgnoredCharsMap,this._sourceMapFilename=a.sourceMapFilename,this._outputFilename=a.outputFilename,this._sourceMapURL=a.sourceMapURL,a.sourceMapBasepath&&(this._sourceMapBasepath=a.sourceMapBasepath.replace(/\\/g,"/")),this._sourceMapRootpath=a.sourceMapRootpath,this._outputSourceFiles=a.outputSourceFiles,this._sourceMapGeneratorConstructor=a.sourceMapGenerator||c("source-map").SourceMapGenerator,this._sourceMapRootpath&&"/"!==this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1)&&(this._sourceMapRootpath+="/"),this._lineNumber=0,this._column=0},a.sourceMapOutput.prototype.normalizeFilename=function(a){return a=a.replace(/\\/g,"/"),this._sourceMapBasepath&&0===a.indexOf(this._sourceMapBasepath)&&(a=a.substring(this._sourceMapBasepath.length),("\\"===a.charAt(0)||"/"===a.charAt(0))&&(a=a.substring(1))),(this._sourceMapRootpath||"")+a},a.sourceMapOutput.prototype.add=function(a,b,c,d){if(a){var e,f,g,h,i;if(b){var j=this._contentsMap[b.filename];this._contentsIgnoredCharsMap[b.filename]&&(c-=this._contentsIgnoredCharsMap[b.filename],0>c&&(c=0),j=j.slice(this._contentsIgnoredCharsMap[b.filename])),j=j.substring(0,c),f=j.split("\n"),h=f[f.length-1]}if(e=a.split("\n"),g=e[e.length-1],b)if(d)for(i=0;i0){var d,e=JSON.stringify(this._sourceMapGenerator.toJSON());this._sourceMapURL?d=this._sourceMapURL:this._sourceMapFilename&&(d=this.normalizeFilename(this._sourceMapFilename)),this._writeSourceMap?this._writeSourceMap(e):d="data:application/json,"+encodeURIComponent(e),d&&this._css.push("/*# sourceMappingURL="+d+" */")}return this._css.join("")}}(c("./tree"));var y=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);w.env=w.env||("127.0.0.1"==location.hostname||"0.0.0.0"==location.hostname||"localhost"==location.hostname||location.port&&location.port.length>0||y?"development":"production");var z={debug:3,info:2,errors:1,none:0};if(w.logLevel="undefined"!=typeof w.logLevel?w.logLevel:"development"===w.env?z.debug:z.errors,w.async=w.async||!1,w.fileAsync=w.fileAsync||!1,w.poll=w.poll||(y?1e3:1500),w.functions)for(var A in w.functions)w.functions.hasOwnProperty(A)&&(w.tree.functions[A]=w.functions[A]);var B=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);B&&(w.dumpLineNumbers=B[1]);var C=/^text\/(x-)?less$/,D=null,E={};if(w.watch=function(){return w.watchMode||(w.env="development",v()),this.watchMode=!0,!0},w.unwatch=function(){return clearInterval(w.watchTimer),this.watchMode=!1,!1},/!watch/.test(location.hash)&&w.watch(),"development"!=w.env)try{D="undefined"==typeof a.localStorage?null:a.localStorage}catch(F){}var G=document.getElementsByTagName("link");w.sheets=[];for(var H=0;H Date: Thu, 11 Oct 2018 09:46:23 +0200 Subject: [PATCH 106/278] moving moments + angular-dynamic-locale --- src/Umbraco.Web.UI.Client/bower.json | 12 ------------ src/Umbraco.Web.UI.Client/gulpfile.js | 21 +++++++++++++++++++++ src/Umbraco.Web.UI.Client/package-lock.json | 18 ++++++++++++++++++ src/Umbraco.Web.UI.Client/package.json | 2 ++ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 66c5ca4827..93d580e1ec 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -17,11 +17,9 @@ ], "dependencies": { "rgrove-lazyload": "*", - "angular-dynamic-locale": "~0.1.36", "tinymce": "~4.7.1", "codemirror": "~5.3.0", "angular-local-storage": "~0.7.1", - "moment": "~2.10.3", "ace-builds": "~1.3.0", "angular-ui-sortable": "0.14.4" }, @@ -34,16 +32,6 @@ "ace-builds" ], "sources": { - "moment": [ - "bower_components/moment/min/moment.min.js", - "bower_components/moment/min/moment-with-locales.js", - "bower_components/moment/min/moment-with-locales.min.js", - "bower_components/moment/locale/*.js" - ], - "angular-dynamic-locale": [ - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js", - "bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map" - ], "angular-local-storage": [ "bower_components/angular-local-storage/dist/angular-local-storage.min.js", "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 6eaa01f822..66fa20206a 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -205,6 +205,14 @@ gulp.task('dependencies', function () { "src": ["./node_modules/angular-cookies/angular-cookies.js"], "base": "./node_modules/angular-cookies" }, + { + "name": "angular-dynamic-locale", + "src": [ + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", + "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map" + ], + "base": "./node_modules/angular-dynamic-locale/dist" + }, { "name": "angular-sanitize", "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], @@ -302,6 +310,19 @@ gulp.task('dependencies', function () { "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], "base": "./node_modules/jquery-validation-unobtrusive/dist" }, + // TODO: We can optimize here: + // we don't have to ship with the moment-with-locales libraries + // we lazyload the user locale + { + "name": "moment", + "src": [ + "./node_modules/moment/min/moment.min.js", + "./node_modules/moment/min/moment-with-locales.js", + "./node_modules/moment/min/moment-with-locales.min.js", + "./node_modules/moment/src/locale/*.js" + ], + "base": "./node_modules/moment/min" + }, { "name": "ng-file-upload", "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index bc7f94c6e0..24922b24ed 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -807,6 +807,11 @@ "to-fast-properties": "^2.0.0" } }, + "@types/angular": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.51.tgz", + "integrity": "sha512-wYU+/zlJWih7ZmonWVjGQ18tG7GboI9asMNjRBM5fpIFJWXSioQttCTw9qGL44cP82ghM8sCV9apEqm1zBDq2w==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -971,6 +976,14 @@ "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" }, + "angular-dynamic-locale": { + "version": "0.1.36", + "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.36.tgz", + "integrity": "sha512-pM4BhBPzLDZRx7iIOYvdqVs5DfBGtXLrAzjUFQQfzr4sCrPMGF6sA0Qw1siP5Gu8rlXbDMQ6/60VE61JHID4Nw==", + "requires": { + "@types/angular": "^1.6.25" + } + }, "angular-i18n": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", @@ -9918,6 +9931,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.10.3", + "resolved": "http://registry.npmjs.org/moment/-/moment-2.10.3.tgz", + "integrity": "sha1-CruZ8wf2UhgwjGk17+KcV7Ggon8=" + }, "morgan": { "version": "1.6.1", "resolved": "http://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ea36b3577b..bc4cbaea55 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -8,6 +8,7 @@ "angular": "1.7.5", "angular-animate": "1.7.5", "angular-cookies": "1.7.5", + "angular-dynamic-locale": "0.1.36", "angular-i18n": "1.7.5", "angular-messages": "1.7.5", "angular-route": "1.7.5", @@ -24,6 +25,7 @@ "jquery-ui": "1.12.1", "jquery-validation": "1.17.0", "jquery-validation-unobtrusive": "3.2.10", + "moment": "2.10.3", "ng-file-upload": "12.2.13", "npm": "^6.4.1", "signalr": "2.2.1", From 9572d195b5abcff38d2967b94953efe8025e1389 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:16:44 +0100 Subject: [PATCH 107/278] Fixes based on Shan's feedback of the PR Only does fancy group logic if more than one group - means other sections can be grouped in the future --- .../Trees/ApplicationTreeController.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 4df0ee0f6a..7fedc44a15 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -22,6 +22,11 @@ namespace Umbraco.Web.Trees [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { + /// + /// Fetches all registered trees and groups them together if they have a [CoreTree] + /// Attribute with a 'TreeGroup' property set + /// This allows the settings section trees to be grouped by Settings, Templating & Other + /// private static readonly Lazy>> CoreTrees = new Lazy>>(() => Current.Services.ApplicationTreeService.GetAllTypes() @@ -86,8 +91,8 @@ namespace Umbraco.Web.Trees } } - //Don't apply fancy grouping logic futher down, if we are not 'settings' section - if(application != Constants.Applications.Settings) + //Don't apply fancy grouping logic futher down, if we only have one group of items + if (CoreTrees.Value.Count() == 1) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); multiTree.Name = Services.TextService.Localize("sections/" + application); @@ -95,28 +100,23 @@ namespace Umbraco.Web.Trees rootNodeGroups.Add(multiTree); return rootNodeGroups; } - - //For settings section only - //Group trees by [CoreTree] attribute - - //Core Trees contains all trees for all sections/applications + + //Group trees by [CoreTree] attribute with a TreeGroup property foreach(var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; var groupNodeCollection = new TreeNodeCollection(); - - //Only add trees to a new collection if they are from 'settings' foreach (var treeItem in treeSectionGroup) { //Item1 tuple - is the type from all tree types var treeItemType = treeItem.Item1; - var findAppTree = appTrees.SingleOrDefault(x => x.GetRuntimeType() == treeItemType); + var findAppTree = appTrees.FirstOrDefault(x => x.GetRuntimeType() == treeItemType); if (findAppTree != null) { //Now we need to get the 'TreeNode' which is in 'collection' - var treeItemNode = collection.SingleOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); + var treeItemNode = collection.FirstOrDefault(x => x.AdditionalData["treeAlias"].ToString() == findAppTree.Alias); if (treeItemNode != null) { @@ -134,7 +134,7 @@ namespace Umbraco.Web.Trees treeGroupName = "thirdPartyGroup"; } - if (groupNodeCollection.Any()) + if (groupNodeCollection.Count > 0) { var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); From eb3fd54e2e033720acc52f10ad196eb2fb18d95f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:21:39 +0100 Subject: [PATCH 108/278] Remove empty/unused keys for these tree header groupings --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 15 +- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 15 +- src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 179 +++++++------ src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 237 +++++++++--------- src/Umbraco.Web.UI/Umbraco/config/lang/he.xml | 29 +-- src/Umbraco.Web.UI/Umbraco/config/lang/it.xml | 13 +- src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml | 23 +- src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 105 ++++---- src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 135 +++++----- src/Umbraco.Web.UI/Umbraco/config/lang/pt.xml | 17 +- src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 225 ++++++++--------- src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 3 - src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 25 +- src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 15 +- .../Umbraco/config/lang/zh_tw.xml | 3 - 18 files changed, 497 insertions(+), 551 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index 95a0a4d661..b218d19ee0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -446,7 +446,7 @@ Stiskněte tlačítko povýšit pro povýšení Vaší databáze na Umbraco %0%

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

    +

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

    -

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

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

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

    @@ -944,9 +944,6 @@ Oprávnění Uživatele Typy Uživatelů Uživatelé - - - Nová aktualizace je připrvena diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index b526f1b47b..2f31f2d24e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1276,9 +1276,6 @@ Mange hilsner fra Umbraco robotten Brugertilladelser Bruger Typer Brugere - - - Ny opdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index cbeb8ab2c5..bdc459d596 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -483,7 +483,7 @@ Umbraco benötigt Schreibrechte auf verschiedene Verzeichnisse, um Dateien wie Bilder oder PDF-Dokumente speichern zu können. Außerdem werden temporäre Daten zur Leistungssteigerung der Website angelegt. Ich möchte mit einem leeren System ohne Inhalte und Vorgaben starten - Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. + Die Website ist zur Zeit komplett leer und ohne Inhalte und Vorgaben zu Erstellung eigener Dokumenttypen und Vorlagen bereit. (<a href="http://Umbraco.tv/documentation/videos/for-site-builders/foundation/document-types">So geht's</a>) Sie können "Runway" auch jederzeit später installieren. Verwenden Sie hierzu den Punkt "Pakete" im Entwickler-Bereich. @@ -497,8 +497,8 @@ Dies sind unsere empfohlenen Module. Schauen Sie sich die an, die Sie installier Ich möchte mit einer einfache Website starten <p> -"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, -aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. +"Runway" ist eine einfache Website mit einfachen Dokumententypen und Vorlagen. Der Installer kann Runway automatisch einrichten, +aber es kann einfach verändert, erweitert oder entfernt werden. Es ist nicht zwingend notwendig und Umbraco kann auch ohne Runway verwendet werden. Runway bietet eine einfache Basis zum schnellen Start mit Umbraco. Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die "Runway Modules" und Ihre Runway-Seite erweitern. </p> @@ -578,13 +578,13 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Ihr freundlicher Umbraco-Robot Hallo %0%

    -

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

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

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

    @@ -927,9 +927,6 @@ Ihr freundlicher Umbraco-Robot Berechtigungen Benutzertypen Benutzer - - - Neues Update verfügbar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 935f6f88b8..f1fb38215a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -410,7 +410,7 @@ añadir prevalor - Tipo de datos GUID Tipo de datos GUIDprestar control @@ -421,7 +421,7 @@ Mostrar etiqueta - Todos los tipos y datos de propiedad usar este tipo de datos lo borrará permanentemente, por favor confirma que quieres borrarlos también @@ -714,41 +714,41 @@ El enlace pulsado es inválido o ha caducado Umbraco: Restaurar contraseña - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Restauración de contraseña requerida

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

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

    -
    -
    + - -


    -
    + + - + ]]> @@ -916,7 +916,7 @@ Define una sección nombrada @section { ... }. Esto se puede mostrar en un área específica de la plantilla madre usando @RenderSection. ]]> Muestra una sección nombrada @@ -1300,13 +1300,13 @@ Campos Incluir subpáginas @@ -1355,9 +1355,6 @@ Hojas de estilo Plantillas Usuarios - - - Existe una nueva actualización @@ -1454,41 +1451,41 @@ Volver a usuarios Umbraco: Invitación - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Hi %0%,

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

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index ee269ff67b..d8642303fb 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -89,7 +89,7 @@ Domaine '%0%' mis à jour Editer les domaines actuels
    Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela + "https://www.example.com/".

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

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

    +

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

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

    - - + + -
    +
    - - + +
    +

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

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

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

    -
    -
    + - -


    -
    - - + +


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

    - - + - +
    +
    - - +
    +

    Salut %0%,

    -

    +

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

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

    Résumé de la mise à jour :

    + +
    MODIFIER
    %6% -
    +

    Bonne journée !

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

    -


    +


    @@ -1549,9 +1549,6 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Modèles Utilisateurs Analytique - - - Nouvelle mise à jour disponible @@ -1649,41 +1646,41 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Retour aux utilisateurs Umbraco: Invitation - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    +
    + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

    Salut %0%,

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

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> Inviter diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml index 4945fcc93a..0fb00ef139 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/he.xml @@ -375,7 +375,7 @@ Database not found! Please check that the information in the "connection string" of the “web.config” file is correct.

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

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

    ]]>
    @@ -386,9 +386,9 @@ Press the upgrade button to upgrade your database to Umbraco %0%

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

    +

    ]]>
    - Press Next to + Press Next to proceed. ]]> next to continue the configuration wizard]]> The Default users’ password needs to be changed!]]> @@ -423,7 +423,7 @@ ]]> אני רוצה להתחיל מאתר ריק learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -437,8 +437,8 @@ ברצוני להתחיל עם אתר פשוט - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

    @@ -459,9 +459,9 @@ You installed Runway, so why not see how your new website looks.]]>
    Further help and information Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> אומברקו %0% מותקנת ומוכנה לשימוש - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, + started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, you can find plenty of resources on our getting started pages.]]>
    Launch Umbraco To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> @@ -515,13 +515,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
    Hi %0%

    -

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

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

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

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

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

    -

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

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

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

    @@ -832,9 +832,6 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Permessi Utente Tipi di Utente Utenti - - - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 6d6ca29748..4b9bda319e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -1073,9 +1073,6 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの権限 ユーザータイプ ユーザー - - - 新しい更新があります diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml index 63654fc0f5..f2d27094a8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ko.xml @@ -408,7 +408,7 @@ Umbraco 는 특정 디렉토리에 쓰기/수정 권한이 필요합니다. 이것은 PDF나 그림과 같은 파일을 저장하고 cache같은 임시데이터을 위해 사용됩니다. scratch를 시작하기 원합니다. learn how) Runway설치를 나중에 실행하실 수 있습니다. 개발도구 부분에서 패키지를 선택하세요. ]]> @@ -445,7 +445,7 @@ 우수 커뮤니티에서 도음을 받으세요. 간단한 사이트제작이나 패키지 사용법, Umbraco기술의 퀵가이드를 제공하는 문서를 보시거나 무료 비디오를 시청하세요.]]>
    Umbraco %0% 가 설치되어 사용준비가 되었습니다. /web.config file을 수동으로 편집해야 합니다. AppSetting 키의 UmbracoConfigurationStatus'%0%'의 값으로 설정하세요.]]> - Umbraco 와 첫만남이시면
    아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. + Umbraco 와 첫만남이시면
    아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다. 시작페이지에서 풍부한 리소소를 제공받을 수 있습니다.]]>
    Umbraco 실행 사이트 관리를 위해서 Umbraco 관리자를 여시고 컨텐츠를 추가하시거나 템플릿과 스타일시트 업데이트 또는 새기능을 추가하세요]]> @@ -492,21 +492,21 @@ 사용자 '%3%' 가 작업 '%1%' 를 페이지 '%2%' 에서 진행했음을 알리는 자동 발송 메일입니다. - + 편집하시려면 http://%4%/#/content/content/edit/%5% 로 이동하세요 좋은 하루 되세요! - + ]]>
    안녕하세요 %0%

    -

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

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

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

    -

    좋은 하루 되세요!

    +

    좋은 하루 되세요!

    ]]>
    %1%에 대한 [%0]알림이 %2%에 생성되었습니다 알림 @@ -779,8 +779,8 @@ %2% 에 의해 문서 '%1%' 가 '%5%' 로 번역요청되었음을 알리는 자동 발송 메일입니다. - 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 - + 편집하시려면 http://%3%/translation/details.aspx?id=%4% 로 + 번역작업을 전반적으로 보시려면 Umbraco에 로그인 하세요 http://%3% @@ -827,9 +827,6 @@ 사용자권한 사용자 유형 사용자 - - - 새 업데이트가 준비되었습니다. diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 58f99a3086..1f3d59bc79 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -912,9 +912,6 @@ Vennlig hilsen Umbraco roboten Brukertillatelser Brukertyper typer Brukere - - - Ny oppdatering er klar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index db737379ff..282f80473b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -285,7 +285,7 @@ Aantal regels Plaats een placeholder id door een ID op uw placeholder te zetten kunt u inhoud plaatsen in deze template vanuit onderliggende templates, door te referreren naar deze ID door gebruik te maken van een <asp:content /> element.]]> - Selecteer een placeholder id uit onderstaande lijst. U kunt alleen + Selecteer een placeholder id uit onderstaande lijst. U kunt alleen Id's kiezen van de master van de huidige template..]]> Klik op de afbeelding voor volledige grootte Kies een item @@ -579,7 +579,7 @@ Ik wil met een eenvoudige website beginnen "Runway" is een eenvoudige website die je van enkele elementaire documenttypes en templates voorziet. De installer kan Runway automatisch voor je opzetten, maar je kunt het gemakkelijk aanpassen, uitbreiden of verwijderen. Het is niet vereist en je kunt Umbraco prima zonder Runway gebruiken. -Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren.

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

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

    -

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

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

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

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

    - - + + -
    +
    - - + +
    +

    Hallo %0%,

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

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> Uitnodigen diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index e108887221..f7125e437c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -87,7 +87,7 @@ Domena '%0%' została zaktualizowana Edytuj Aktualne Domeny Odziedziczona Język @@ -335,9 +335,9 @@ Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktualna, lecz nieopublikowana zawartość pozostanie niewidoczna Liczba kolumn Liczba wierszy - Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, + Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]> - Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko + Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko spośród ID na szablonie nadrzędnym tego formularza.]]> Kliknij na obrazie, aby zobaczyć go w pełnym rozmiarze Wybierz element @@ -626,23 +626,23 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umbraco %0% ]]> Dalej, aby kontynuować.]]> - Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

    -

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

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

    +

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

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

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

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

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

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

    + Twoje ustawienia uprawnień są prawie idealne!

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

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

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

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

    + Twoje ustawienia uprawnień są idealne!

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

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

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

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

    Wciśnij "dalej", aby rozpocząć proces konfigruacji.]]>
    @@ -778,11 +778,11 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Edytuj powiadomienie dla %0% instaluj, aby zainstalować bazę danych Umb Pozdrowienia od robota Umbraco ]]> - Witaj %0%

    + Witaj %0%

    -

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

    -
    +

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

    + -

    -

    Podsumowanie zmian:

    - - %6% -
    -

    +      EDYTUJ        +
    +
    +

    +

    Podsumowanie zmian:

    + + %6% +
    +

    - + -

    Miłego dnia!

    - Pozdrowienia od robota Umbraco +

    Miłego dnia!

    + Pozdrowienia od robota Umbraco

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

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

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

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

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

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

    -

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

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

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

    @@ -816,9 +816,6 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Permissões de usuário Tipos de Usuários Usuários - - - Nova atualização pronta diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 3b09c34818..7fe4433e38 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -930,41 +930,41 @@ Ссылка, по которой Вы попали сюда, неверна или устарела Umbraco: сброс пароля - - - - - - + + + + + + + - - + +
    +
    - - - + + + -
    - -
    - -
    + +
    + +
    -
    + - + - - - + + +
    +

    - - + + -
    +
    - - + +
    +

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

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

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

    -
    -
    + - -


    -
    - - + +


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

    - - + - +
    +
    - - +
    +

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

    -

    +

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

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

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

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

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

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

    -


    +


    @@ -1668,9 +1668,6 @@ Стили CSS Шаблоны Пользователи - - - Доступны обновления @@ -1706,41 +1703,41 @@ Пригласить Приглашение в панель администрирования Umbraco

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

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

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

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

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

    %3%

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

    - - + + -
    +
    - - + +
    +

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

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

    -
    -
    + - -


    -
    - - + +


    +
    + + - + ]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 5434f6bb47..f16129b315 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -856,9 +856,6 @@ Användarrättigheter Användartyper Användare - - - Ny uppdatering tillgänglig diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index bd39a6fba8..41640faa4a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -55,7 +55,7 @@ etki /> bir düzey yollar desteklenir
    Miras Al Kültür @@ -421,7 +421,7 @@ Veritabanı bulunamadı! "Web.config" nin "bağlantı dizesinde" bilgi dosyası doğru olup olmadığını kontrol edin.

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

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

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

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

    +

    ]]> Sonraki işlem. ]]> next
    to continue the configuration wizard]]> @@ -470,7 +470,7 @@ ]]> Baştan başlamak istiyorum learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ]]> @@ -484,8 +484,8 @@ I want to start with a simple website - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.

    @@ -506,9 +506,9 @@ You installed Runway, so why not see how your new website looks.]]>
    Further help and information Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, + started instantly by clicking the "Launch Umbraco" button below.
    If you are new to Umbraco, you can find plenty of resources on our getting started pages.]]>
    Launch Umbraco To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> @@ -575,13 +575,13 @@ To manage your website, simply open the Umbraco back office and start adding con ]]>
    Hi %0%

    -

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

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

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

    @@ -936,9 +936,6 @@ To manage your website, simply open the Umbraco back office and start adding con Scriptler Stil dosyaları Şablonlar - - - Yeni bir güncelleme geldi diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index e6d013e4d1..3945075d29 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -60,7 +60,7 @@ 只需要设置语言部分即可。]]>
    继承 语言 - + 也可以从父节点继承。]]> 域名 @@ -543,7 +543,7 @@ 点击更新来更新系统到 %0%

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

    +

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

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

    @@ -1073,7 +1073,7 @@ %0%: 您好!这是一封自动邮件来提醒您注意,%2%的文档'%1%' - 需要您翻译为'%5%' + 需要您翻译为'%5%' 转到 http://%3%/translation/details.aspx?id=%4% 进行编辑 @@ -1128,9 +1128,6 @@ Users 分部视图 分部视图宏文件 - - - 有可用更新 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index 9ca2278182..03282f6781 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -1108,9 +1108,6 @@ 腳本 樣式表 範本 - - - 有可用更新 From 6dd9bf56be3c068711333908256a1bbde437a1ea Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 09:23:42 +0100 Subject: [PATCH 109/278] Update logic for checking for groups to be more robust (as we could still have one named group) --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7fedc44a15..1a0db84805 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -92,7 +92,8 @@ namespace Umbraco.Web.Trees } //Don't apply fancy grouping logic futher down, if we only have one group of items - if (CoreTrees.Value.Count() == 1) + var hasGroups = CoreTrees.Value.Any(x => x.Key != null); + if (hasGroups == false) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); multiTree.Name = Services.TextService.Localize("sections/" + application); From f996653a6758baa8bf01ddec9f2974d38e4a7e04 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 11 Oct 2018 10:35:23 +0200 Subject: [PATCH 110/278] changes logic to Count --- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 1a0db84805..7e73f44267 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -27,8 +27,8 @@ namespace Umbraco.Web.Trees /// Attribute with a 'TreeGroup' property set /// This allows the settings section trees to be grouped by Settings, Templating & Other ///
    - private static readonly Lazy>> CoreTrees - = new Lazy>>(() => + private static readonly Lazy>> CoreTrees + = new Lazy>>(() => Current.Services.ApplicationTreeService.GetAllTypes() .Select(x => (TreeType: x, TreeGroup: x.GetCustomAttribute(false)?.TreeGroup)) .GroupBy(x => x.TreeGroup) @@ -92,7 +92,7 @@ namespace Umbraco.Web.Trees } //Don't apply fancy grouping logic futher down, if we only have one group of items - var hasGroups = CoreTrees.Value.Any(x => x.Key != null); + var hasGroups = CoreTrees.Value.Count > 0; if (hasGroups == false) { var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); From a0c7a45a4c8d6334606420fbc66fba7dc0891327 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 10:44:14 +0200 Subject: [PATCH 111/278] move, move, move all day loong... --- src/Umbraco.Web.UI.Client/bower.json | 14 +-- src/Umbraco.Web.UI.Client/gulpfile.js | 105 +++++++++++--------- src/Umbraco.Web.UI.Client/package-lock.json | 25 +++++ src/Umbraco.Web.UI.Client/package.json | 5 + 4 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 93d580e1ec..d0e72f620d 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -16,12 +16,7 @@ "tests" ], "dependencies": { - "rgrove-lazyload": "*", - "tinymce": "~4.7.1", - "codemirror": "~5.3.0", - "angular-local-storage": "~0.7.1", - "ace-builds": "~1.3.0", - "angular-ui-sortable": "0.14.4" + "rgrove-lazyload": "*" }, "install": { "path": "lib-bower", @@ -32,13 +27,6 @@ "ace-builds" ], "sources": { - "angular-local-storage": [ - "bower_components/angular-local-storage/dist/angular-local-storage.min.js", - "bower_components/angular-local-storage/dist/angular-local-storage.min.js.map" - ], - "tinymce": [ - "bower_components/tinymce/tinymce.min.js" - ], "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js" } }, diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 66fa20206a..a099db5040 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -144,57 +144,28 @@ gulp.task('docserve', function(cb) { **************************/ gulp.task('dependencies', function () { - //bower component/npm specific copy rules - //this is to patch the sometimes wonky rules these libs are distrbuted under - //as we do multiple things in this task, we merge the multiple streams var stream = new MergeStream(); - //Tinymce plugins/themes - stream.add( - gulp.src(["./bower_components/tinymce/plugins/**", - "./bower_components/tinymce/themes/**"], - { base: "./bower_components/tinymce/" }) - .pipe(gulp.dest(root + targets.lib + "/tinymce")) - ); - - // ace Editor - stream.add( - gulp.src(["bower_components/ace-builds/src-min-noconflict/ace.js", - "bower_components/ace-builds/src-min-noconflict/ext-language_tools.js", - "bower_components/ace-builds/src-min-noconflict/ext-searchbox.js", - "bower_components/ace-builds/src-min-noconflict/ext-settings_menu.js", - "bower_components/ace-builds/src-min-noconflict/snippets/text.js", - "bower_components/ace-builds/src-min-noconflict/snippets/javascript.js", - "bower_components/ace-builds/src-min-noconflict/theme-chrome.js", - "bower_components/ace-builds/src-min-noconflict/mode-razor.js", - "bower_components/ace-builds/src-min-noconflict/mode-javascript.js", - "bower_components/ace-builds/src-min-noconflict/worker-javascript.js"], - { base: "./bower_components/ace-builds/" }) - .pipe(gulp.dest(root + targets.lib + "/ace-builds")) - ); - - // code mirror - stream.add( - gulp.src([ - "bower_components/codemirror/lib/codemirror.js", - "bower_components/codemirror/lib/codemirror.css", - - "bower_components/codemirror/mode/css/*", - "bower_components/codemirror/mode/javascript/*", - "bower_components/codemirror/mode/xml/*", - "bower_components/codemirror/mode/htmlmixed/*", - - "bower_components/codemirror/addon/search/*", - "bower_components/codemirror/addon/edit/*", - "bower_components/codemirror/addon/selection/*", - "bower_components/codemirror/addon/dialog/*"], - { base: "./bower_components/codemirror/" }) - .pipe(gulp.dest(root + targets.lib + "/codemirror")) - ); - - // npm dependencies + // Pick the dependencies we need from each package + // so we don't just ship with a lot of files that aren't needed const nodeModules = [ + { + "name": "ace-builds", + "src": [ + "./node_modules/ace-builds/src-min-noconflict/ace.js", + "./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js", + "./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js", + "./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/text.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", + "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", + "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", + "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", + "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js" + ], + "base": "./node_modules/ace-builds" + }, { "name": "angular", "src": ["./node_modules/angular/angular.js"], @@ -223,6 +194,11 @@ gulp.task('dependencies', function () { "src": ["./node_modules/angular-touch/angular-touch.js"], "base": "./node_modules/angular-touch" }, + { + "name": "angular-ui-sortable", + "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], + "base": "./node_modules/angular-ui-sortable/dist" + }, { "name": "angular-route", "src": ["./node_modules/angular-route/angular-route.js"], @@ -241,6 +217,14 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/angular-i18n" }, + { + "name": "angular-local-storage", + "src": [ + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" + ], + "base": "./node_modules/angular-local-storage/dist" + }, { "name": "angular-messages", "src": ["./node_modules/angular-messages/angular-messages.js"], @@ -261,6 +245,24 @@ gulp.task('dependencies', function () { "src": ["./node_modules/clipboard/dist/clipboard.min.js"], "base": "./node_modules/clipboard/dist" }, + { + "name": "codemirror", + "src": [ + "./node_modules/codemirror/lib/codemirror.js", + "./node_modules/codemirror/lib/codemirror.css", + + "./node_modules/codemirror/mode/css/*", + "./node_modules/codemirror/mode/javascript/*", + "./node_modules/codemirror/mode/xml/*", + "./node_modules/codemirror/mode/htmlmixed/*", + + "./node_modules/codemirror/addon/search/*", + "./node_modules/codemirror/addon/edit/*", + "./node_modules/codemirror/addon/selection/*", + "./node_modules/codemirror/addon/dialog/*" + ], + "base": "./node_modules/codemirror" + }, { "name": "jsdiff", "src": ["./node_modules/diff/dist/diff.min.js"], @@ -333,6 +335,15 @@ gulp.task('dependencies', function () { "src": ["./node_modules/signalr/jquery.signalR.js"], "base": "./node_modules/signalr" }, + { + "name": "tinymce", + "src": [ + "./node_modules/tinymce/tinymce.min.js", + "./node_modules/tinymce/plugins/**", + "./node_modules/tinymce/themes/**" + ], + "base": "./node_modules/tinymce" + }, { "name": "typeahead.js", "src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"], diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 24922b24ed..e9d187d32d 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -858,6 +858,11 @@ } } }, + "ace-builds": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.3.0.tgz", + "integrity": "sha512-RxLL7ZV+2dP5VZRpaqWvxMru9+Rpbe4A9L0ajbM3ifiW5IkdlIXHNy0lafzJign/v8E9PkBXlOH+2HuiTRD46Q==" + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -989,6 +994,11 @@ "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" }, + "angular-local-storage": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/angular-local-storage/-/angular-local-storage-0.7.1.tgz", + "integrity": "sha1-+9JzB2PCn6mvVyXgGGx4BiHozdI=" + }, "angular-messages": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", @@ -1009,6 +1019,11 @@ "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" }, + "angular-ui-sortable": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.15.0.tgz", + "integrity": "sha1-8kLlH4Uic34+9ndUC3J4dyxQzCA=" + }, "animejs": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", @@ -2345,6 +2360,11 @@ "q": "^1.1.2" } }, + "codemirror": { + "version": "5.3.0", + "resolved": "http://registry.npmjs.org/codemirror/-/codemirror-5.3.0.tgz", + "integrity": "sha1-JDyyaN1hynjdsn6C8uws74j7lGE=" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -16160,6 +16180,11 @@ } } }, + "tinymce": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.1.tgz", + "integrity": "sha1-iMhpQCt8mf5t8oAbxzJFD9rU9QA=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index bc4cbaea55..0c85091ae9 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -5,18 +5,22 @@ "build": "gulp" }, "dependencies": { + "ace-builds": "1.3.0", "angular": "1.7.5", "angular-animate": "1.7.5", "angular-cookies": "1.7.5", "angular-dynamic-locale": "0.1.36", "angular-i18n": "1.7.5", + "angular-local-storage": "0.7.1", "angular-messages": "1.7.5", "angular-route": "1.7.5", "angular-sanitize": "1.7.5", "angular-touch": "1.7.5", + "angular-ui-sortable": "0.15.0", "animejs": "2.2.0", "bootstrap-social": "4.8.0", "clipboard": "2.0.0", + "codemirror": "5.3.0", "diff": "3.4.0", "flatpickr": "4.5.2", "font-awesome": "4.2.0", @@ -29,6 +33,7 @@ "ng-file-upload": "12.2.13", "npm": "^6.4.1", "signalr": "2.2.1", + "tinymce": "4.7.1", "typeahead.js": "0.10.5", "underscore": "1.9.1" }, From 00bad829c3d22dd831e2437e5c12992f8c035768 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 11 Oct 2018 11:54:28 +0200 Subject: [PATCH 112/278] Removed rogue colon --- .../Persistence/Repositories/Implement/DocumentRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 12aed212fc..016e7d4f02 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -721,7 +721,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : filterClause.Item1; filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)," : $"AND ({where})", + where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", filterClause.Item2); } } From 7ebccb1a369c353c2bdee30757c37014b2dd7a89 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 10:54:45 +0100 Subject: [PATCH 113/278] Don't need to globally install bower anymore in this helper batch script --- src/Umbraco.Web.UI.Client/setupgulp.bat | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/setupgulp.bat b/src/Umbraco.Web.UI.Client/setupgulp.bat index 5cddec3747..6a53e11b71 100644 --- a/src/Umbraco.Web.UI.Client/setupgulp.bat +++ b/src/Umbraco.Web.UI.Client/setupgulp.bat @@ -12,12 +12,11 @@ if /I "%c%" EQU "N" goto :eof :setupgulp call npm install -call npm -g install bower call npm -g install gulp call npm -g install gulp-cli ECHO. ECHO. -ECHO You should now be able to run: gulp build +ECHO You should now be able to run: gulp build or gulp dev ECHO. ECHO. \ No newline at end of file From bd20518d507a56719e2cb336800f8148f6e26da4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 12:16:09 +0200 Subject: [PATCH 114/278] move the last dependencies --- src/Umbraco.Web.UI.Client/bower.json | 5 ----- src/Umbraco.Web.UI.Client/gulpfile.js | 10 ++++++++++ src/Umbraco.Web.UI.Client/package-lock.json | 10 ++++++++++ src/Umbraco.Web.UI.Client/package.json | 2 ++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index d0e72f620d..f2c436008d 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -16,7 +16,6 @@ "tests" ], "dependencies": { - "rgrove-lazyload": "*" }, "install": { "path": "lib-bower", @@ -27,10 +26,6 @@ "ace-builds" ], "sources": { - "rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js" } - }, - "devDependencies": { - "angular-mocks": "~1.7.2" } } diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index a099db5040..609b6751d2 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -230,6 +230,11 @@ gulp.task('dependencies', function () { "src": ["./node_modules/angular-messages/angular-messages.js"], "base": "./node_modules/angular-messages" }, + { + "name": "angular-mocks", + "src": ["./node_modules/angular/angular-mocks.js"], + "base": "./node_modules/angular-mocks" + }, { "name": "animejs", "src": ["./node_modules/animejs/anime.min.js"], @@ -312,6 +317,11 @@ gulp.task('dependencies', function () { "src": ["./node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"], "base": "./node_modules/jquery-validation-unobtrusive/dist" }, + { + "name": "rgrove-lazyload", + "src": ["./node_modules/lazyload-js/lazyload.js"], + "base": "./node_modules/lazyload-js" + }, // TODO: We can optimize here: // we don't have to ship with the moment-with-locales libraries // we lazyload the user locale diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index e9d187d32d..125c07ae21 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1004,6 +1004,11 @@ "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" }, + "angular-mocks": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", + "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" + }, "angular-route": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", @@ -8919,6 +8924,11 @@ "dev": true, "optional": true }, + "lazyload-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazyload-js/-/lazyload-js-1.0.0.tgz", + "integrity": "sha1-jBA5sbaRec1J/cMkICOvSM4IOSU=" + }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 0c85091ae9..f63f09cf13 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -13,6 +13,7 @@ "angular-i18n": "1.7.5", "angular-local-storage": "0.7.1", "angular-messages": "1.7.5", + "angular-mocks": "1.7.5", "angular-route": "1.7.5", "angular-sanitize": "1.7.5", "angular-touch": "1.7.5", @@ -29,6 +30,7 @@ "jquery-ui": "1.12.1", "jquery-validation": "1.17.0", "jquery-validation-unobtrusive": "3.2.10", + "lazyload-js": "1.0.0", "moment": "2.10.3", "ng-file-upload": "12.2.13", "npm": "^6.4.1", From a73e2888468e7f6acecca267b7164e8ac7bf91f8 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:52:57 +0200 Subject: [PATCH 115/278] Few fixes - including completing a transaction, disabling the notifications so they can be handled manually. --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 ++ .../common/directives/components/content/edit.controller.js | 3 ++- .../src/views/content/overlays/sendtopublish.controller.js | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..48284d8138 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1825,6 +1825,8 @@ namespace Umbraco.Core.Services.Implement sendToPublishEventArgs.CanCancel = false; scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + scope.Complete(); } return true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f452e6b812..c0f0c9724c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -473,7 +473,8 @@ //we need to return this promise so that the dialog can handle the result and wire up the validation response return performSave({ saveMethod: contentResource.sendToPublish, - action: "sendToPublish" + action: "sendToPublish", + showNotifications: false }).then(function (data) { //show all notifications manually here since we disabled showing them automatically in the save method formHelper.showNotifications(data); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index d608ed7f27..c95860cdec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -69,10 +69,9 @@ return (variant.state === "Published" && !variant.isDirty && !variant.active || variant.state === "NotCreated" && !variant.isDirty && !variant.active); } - //when this dialog is closed, reset all 'sendToPublish' flags + //when this dialog is closed, reset all 'save' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { - vm.variants[i].sendToPublish = false; vm.variants[i].save = false; } }); From 79c363c9009f5870e11c4ef927d2872866971c65 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:54:06 +0200 Subject: [PATCH 116/278] Updating the admin user id to the correct userId --- src/Umbraco.Core/Services/Implement/NotificationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 3afb7c3777..cc76374715 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -72,7 +72,7 @@ namespace Umbraco.Core.Services.Implement // users being (dis)approved = not an issue, filtered in memory not in SQL // users being modified or created = not an issue, ordering by ID, as long as we don't *insert* low IDs // users being deleted = not an issue for GetNextUsers - var id = 0; + var id = Constants.Security.SuperUserId; var nodeIds = content.Path.Split(',').Select(int.Parse).ToArray(); const int pagesz = 400; // load batches of 400 users do From e1c7d23c81eabe4e9a6253368d78ebad148a1743 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 12:54:32 +0200 Subject: [PATCH 117/278] Adding a missing parameter --- .../src/common/resources/content.resource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..61dfe918c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -756,11 +756,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - sendToPublish: function (content, isNew, files) { + sendToPublish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, /** From bb5817ca9264e41a78c474126689ee0a71a400c0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 13:12:10 +0200 Subject: [PATCH 118/278] fixing versions and files --- src/Umbraco.Web.UI.Client/gulpfile.js | 16 ++++++++-------- src/Umbraco.Web.UI.Client/package.json | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 609b6751d2..9fd029d970 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -256,10 +256,10 @@ gulp.task('dependencies', function () { "./node_modules/codemirror/lib/codemirror.js", "./node_modules/codemirror/lib/codemirror.css", - "./node_modules/codemirror/mode/css/*", - "./node_modules/codemirror/mode/javascript/*", - "./node_modules/codemirror/mode/xml/*", - "./node_modules/codemirror/mode/htmlmixed/*", + "./node_modules/codemirror/mode/css/css.js", + "./node_modules/codemirror/mode/javascript/javascript.js", + "./node_modules/codemirror/mode/xml/xml.js", + "./node_modules/codemirror/mode/htmlmixed/htmlmixed.js", "./node_modules/codemirror/addon/search/*", "./node_modules/codemirror/addon/edit/*", @@ -299,12 +299,12 @@ gulp.task('dependencies', function () { }, { "name": "jquery-migrate", - "src": ["./node_modules/jquery-migrate/jquery-migrate.min.js"], - "base": "./node_modules/jquery-migrate" + "src": ["./node_modules/jquery-migrate/dist/jquery-migrate.min.js"], + "base": "./node_modules/jquery-migrate/dist" }, { "name": "jquery-ui", - "src": ["./node_modules/jquery-ui/jquery-ui.min.js"], + "src": ["./node_modules/jquery-ui/ui/widget.js"], "base": "./node_modules/jquery-ui" }, { @@ -331,7 +331,7 @@ gulp.task('dependencies', function () { "./node_modules/moment/min/moment.min.js", "./node_modules/moment/min/moment-with-locales.js", "./node_modules/moment/min/moment-with-locales.min.js", - "./node_modules/moment/src/locale/*.js" + "./node_modules/moment/locale/*.js" ], "base": "./node_modules/moment/min" }, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index f63f09cf13..74a7029ed3 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -9,9 +9,9 @@ "angular": "1.7.5", "angular-animate": "1.7.5", "angular-cookies": "1.7.5", - "angular-dynamic-locale": "0.1.36", + "angular-dynamic-locale": "0.1.37", "angular-i18n": "1.7.5", - "angular-local-storage": "0.7.1", + "angular-local-storage": "0.7.0", "angular-messages": "1.7.5", "angular-mocks": "1.7.5", "angular-route": "1.7.5", @@ -34,8 +34,8 @@ "moment": "2.10.3", "ng-file-upload": "12.2.13", "npm": "^6.4.1", - "signalr": "2.2.1", - "tinymce": "4.7.1", + "signalr": "2.3.0", + "tinymce": "4.7.13", "typeahead.js": "0.10.5", "underscore": "1.9.1" }, From efefa54601333a4736dcdcf1ddd38d2c552cc7b5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 13:26:21 +0200 Subject: [PATCH 119/278] remove all bower logic in gulp + remove bower.json --- src/Umbraco.Web.UI.Client/bower.json | 31 -------------------------- src/Umbraco.Web.UI.Client/gulpfile.js | 13 +++-------- src/Umbraco.Web.UI.Client/package.json | 1 - 3 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/bower.json diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json deleted file mode 100644 index f2c436008d..0000000000 --- a/src/Umbraco.Web.UI.Client/bower.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "umbraco", - "version": "7", - "homepage": "https://github.com/umbraco/Umbraco-CMS", - "authors": [ - "Shannon " - ], - "description": "Umbraco CMS", - "license": "MIT", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - }, - "install": { - "path": "lib-bower", - "ignore": [ - "font-awesome", - "bootstrap", - "codemirror", - "ace-builds" - ], - "sources": { - } - } -} diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 9fd029d970..c9eb6eaca1 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -105,7 +105,6 @@ var sources = { less: "./src/less/**/*.less", js: "./src/*.js", lib: "./lib/**/*", - bower: "./lib-bower/**/*", assets: "./src/assets/**" } }; @@ -318,8 +317,8 @@ gulp.task('dependencies', function () { "base": "./node_modules/jquery-validation-unobtrusive/dist" }, { - "name": "rgrove-lazyload", - "src": ["./node_modules/lazyload-js/lazyload.js"], + "name": "lazyload-js", + "src": ["./node_modules/lazyload-js/lazyload-min.js"], "base": "./node_modules/lazyload-js" }, // TODO: We can optimize here: @@ -375,18 +374,12 @@ gulp.task('dependencies', function () { ); }); - //copy over libs which are not on bower (/lib) and - //libraries that have been managed by bower-installer (/lib-bower) + //copy over libs which are not on npm (/lib) stream.add( gulp.src(sources.globs.lib) .pipe(gulp.dest(root + targets.lib)) ); - stream.add( - gulp.src(sources.globs.bower) - .pipe(gulp.dest(root + targets.lib)) - ); - //Copies all static assets into /root / assets folder //css, fonts and image files stream.add( diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 74a7029ed3..713a85ddc1 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,6 +1,5 @@ { "scripts": { - "install": "bower-installer", "test": "karma start test/config/karma.conf.js --singlerun", "build": "gulp" }, From 09f67c6c4397d5f999b450030e568e38b9df2026 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 13:26:35 +0200 Subject: [PATCH 120/278] fix version --- src/Umbraco.Web.UI.Client/package-lock.json | 18 +++++++++--------- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 125c07ae21..fda781c296 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -982,9 +982,9 @@ "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" }, "angular-dynamic-locale": { - "version": "0.1.36", - "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.36.tgz", - "integrity": "sha512-pM4BhBPzLDZRx7iIOYvdqVs5DfBGtXLrAzjUFQQfzr4sCrPMGF6sA0Qw1siP5Gu8rlXbDMQ6/60VE61JHID4Nw==", + "version": "0.1.37", + "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", + "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", "requires": { "@types/angular": "^1.6.25" } @@ -15104,9 +15104,9 @@ "dev": true }, "signalr": { - "version": "2.2.1", - "resolved": "http://registry.npmjs.org/signalr/-/signalr-2.2.1.tgz", - "integrity": "sha1-YRI0QP2iqa1799/KHhr2eFPB3Sw=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/signalr/-/signalr-2.3.0.tgz", + "integrity": "sha512-NrvIGftLz3QVujdjCvaNso56ltTr1FowSR0DCIaSOJ3J4t5pTebTfnh2VT0HHIM3PJ/v15lukIL4y+8MMknqzg==", "requires": { "jquery": ">=1.6.4" } @@ -16191,9 +16191,9 @@ } }, "tinymce": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.1.tgz", - "integrity": "sha1-iMhpQCt8mf5t8oAbxzJFD9rU9QA=" + "version": "4.7.13", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.7.13.tgz", + "integrity": "sha512-6QbNYGV4VExH+p7+o/5km6jOnVSD5mO7aw0s+eKByKnpyG8gZfajxXPhwBM57r7SIravrCI6LFj8DARNe31qPw==" }, "tmp": { "version": "0.0.33", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 713a85ddc1..a20e94af56 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -10,7 +10,7 @@ "angular-cookies": "1.7.5", "angular-dynamic-locale": "0.1.37", "angular-i18n": "1.7.5", - "angular-local-storage": "0.7.0", + "angular-local-storage": "0.7.1", "angular-messages": "1.7.5", "angular-mocks": "1.7.5", "angular-route": "1.7.5", From 7a372bf8149c32d09bd87894e05f4ed64bb78eff Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 13:50:52 +0200 Subject: [PATCH 121/278] fix lazyload --- src/Umbraco.Web.UI.Client/gulpfile.js | 4 ++-- src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index c9eb6eaca1..30c4826d8d 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -139,7 +139,7 @@ gulp.task('docserve', function(cb) { }); /************************** - * Task processes and copies all dependencies, either installed by bower, npm or stored locally in the project + * Task processes and copies all dependencies, either installed by npm or stored locally in the project **************************/ gulp.task('dependencies', function () { @@ -318,7 +318,7 @@ gulp.task('dependencies', function () { }, { "name": "lazyload-js", - "src": ["./node_modules/lazyload-js/lazyload-min.js"], + "src": ["./node_modules/lazyload-js/lazyload.min.js"], "base": "./node_modules/lazyload-js" }, // TODO: We can optimize here: diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 83fc836841..3cdf65ffd8 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -124,7 +124,7 @@ } - + @if (isDebug) From a00875089c6cb695b06d4bbccb7a0df0f1792dfc Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 14:10:18 +0200 Subject: [PATCH 122/278] get the right version of ace --- src/Umbraco.Web.UI.Client/package-lock.json | 6 +++--- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index fda781c296..1053e0bc64 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -859,9 +859,9 @@ } }, "ace-builds": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.3.0.tgz", - "integrity": "sha512-RxLL7ZV+2dP5VZRpaqWvxMru9+Rpbe4A9L0ajbM3ifiW5IkdlIXHNy0lafzJign/v8E9PkBXlOH+2HuiTRD46Q==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.3.3.tgz", + "integrity": "sha512-PbSdoHw42kt5vaXkEVSfUYCd3K1BCfAvyXW9TvR/2ATkk65oImjS1v0evHmzHhOYPSTUO8BprvmpfYT9Vp2akA==" }, "acorn": { "version": "5.7.3", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index a20e94af56..323ee63dd6 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -4,7 +4,7 @@ "build": "gulp" }, "dependencies": { - "ace-builds": "1.3.0", + "ace-builds": "1.3.3", "angular": "1.7.5", "angular-animate": "1.7.5", "angular-cookies": "1.7.5", From ecaedc547af80a551347e9ed8d782a4816feef2a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 13:31:54 +0100 Subject: [PATCH 123/278] Fixes moment locale copying --- src/Umbraco.Web.UI.Client/gulpfile.js | 110 ++++++++++++++------------ 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 30c4826d8d..ffcec3b3be 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -32,7 +32,7 @@ var karmaServer = require('karma').Server; Helper functions ***************************************************************/ function processJs(files, out) { - + return gulp.src(files) // check for js errors .pipe(eslint()) @@ -60,7 +60,7 @@ function processLess(files, out) { .pipe(postcss(processors)) .pipe(rename(out)) .pipe(gulp.dest(root + targets.css)); - + console.log(out + " compiled"); } @@ -69,7 +69,7 @@ Paths and destinations Each group is iterated automatically in the setup tasks below ***************************************************************/ var sources = { - + //less files used by backoffice and preview //processed in the less task less: { @@ -105,7 +105,7 @@ var sources = { less: "./src/less/**/*.less", js: "./src/*.js", lib: "./lib/**/*", - assets: "./src/assets/**" + assets: "./src/assets/**" } }; @@ -141,15 +141,15 @@ gulp.task('docserve', function(cb) { /************************** * Task processes and copies all dependencies, either installed by npm or stored locally in the project **************************/ -gulp.task('dependencies', function () { +gulp.task('dependencies', function () { //as we do multiple things in this task, we merge the multiple streams var stream = new MergeStream(); - // Pick the dependencies we need from each package + // Pick the dependencies we need from each package // so we don't just ship with a lot of files that aren't needed const nodeModules = [ - { + { "name": "ace-builds", "src": [ "./node_modules/ace-builds/src-min-noconflict/ace.js", @@ -165,17 +165,17 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/ace-builds" }, - { + { "name": "angular", "src": ["./node_modules/angular/angular.js"], "base": "./node_modules/angular" }, - { + { "name": "angular-cookies", "src": ["./node_modules/angular-cookies/angular-cookies.js"], "base": "./node_modules/angular-cookies" }, - { + { "name": "angular-dynamic-locale", "src": [ "./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js", @@ -183,83 +183,83 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/angular-dynamic-locale/dist" }, - { + { "name": "angular-sanitize", "src": ["./node_modules/angular-sanitize/angular-sanitize.js"], "base": "./node_modules/angular-sanitize" }, - { + { "name": "angular-touch", "src": ["./node_modules/angular-touch/angular-touch.js"], "base": "./node_modules/angular-touch" }, - { + { "name": "angular-ui-sortable", "src": ["./node_modules/angular-ui-sortable/dist/sortable.js"], "base": "./node_modules/angular-ui-sortable/dist" }, - { + { "name": "angular-route", "src": ["./node_modules/angular-route/angular-route.js"], "base": "./node_modules/angular-route" }, - { + { "name": "angular-animate", "src": ["./node_modules/angular-animate/angular-animate.js"], "base": "./node_modules/angular-animate" }, - { + { "name": "angular-i18n", "src": [ - "./node_modules/angular-i18n/angular-i18n.js", + "./node_modules/angular-i18n/angular-i18n.js", "./node_modules/angular-i18n/angular-locale_*.js" ], "base": "./node_modules/angular-i18n" }, - { + { "name": "angular-local-storage", "src": [ - "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", + "./node_modules/angular-local-storage/dist/angular-local-storage.min.js", "./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map" ], "base": "./node_modules/angular-local-storage/dist" }, - { + { "name": "angular-messages", "src": ["./node_modules/angular-messages/angular-messages.js"], "base": "./node_modules/angular-messages" }, - { + { "name": "angular-mocks", "src": ["./node_modules/angular/angular-mocks.js"], "base": "./node_modules/angular-mocks" }, - { + { "name": "animejs", "src": ["./node_modules/animejs/anime.min.js"], "base": "./node_modules/animejs" }, - { + { "name": "bootstrap-social", "src": ["./node_modules/bootstrap-social/bootstrap-social.css"], "base": "./node_modules/bootstrap-social" }, - { + { "name": "clipboard", "src": ["./node_modules/clipboard/dist/clipboard.min.js"], "base": "./node_modules/clipboard/dist" }, - { + { "name": "codemirror", "src": [ "./node_modules/codemirror/lib/codemirror.js", "./node_modules/codemirror/lib/codemirror.css", - + "./node_modules/codemirror/mode/css/css.js", "./node_modules/codemirror/mode/javascript/javascript.js", "./node_modules/codemirror/mode/xml/xml.js", "./node_modules/codemirror/mode/htmlmixed/htmlmixed.js", - + "./node_modules/codemirror/addon/search/*", "./node_modules/codemirror/addon/edit/*", "./node_modules/codemirror/addon/selection/*", @@ -267,7 +267,7 @@ gulp.task('dependencies', function () { ], "base": "./node_modules/codemirror" }, - { + { "name": "jsdiff", "src": ["./node_modules/diff/dist/diff.min.js"], "base": "./node_modules/diff/dist" @@ -275,7 +275,7 @@ gulp.task('dependencies', function () { { "name": "flatpickr", "src": [ - "./node_modules/flatpickr/dist/flatpickr.js", + "./node_modules/flatpickr/dist/flatpickr.js", "./node_modules/flatpickr/dist/flatpickr.css" ], "base": "./node_modules/flatpickr/dist" @@ -283,7 +283,7 @@ gulp.task('dependencies', function () { { "name": "font-awesome", "src": [ - "./node_modules/font-awesome/fonts/*", + "./node_modules/font-awesome/fonts/*", "./node_modules/font-awesome/css/font-awesome.min.css" ], "base": "./node_modules/font-awesome" @@ -291,7 +291,7 @@ gulp.task('dependencies', function () { { "name": "jquery", "src": [ - "./node_modules/jquery/dist/jquery.min.js", + "./node_modules/jquery/dist/jquery.min.js", "./node_modules/jquery/dist/jquery.min.map" ], "base": "./node_modules/jquery/dist" @@ -330,10 +330,16 @@ gulp.task('dependencies', function () { "./node_modules/moment/min/moment.min.js", "./node_modules/moment/min/moment-with-locales.js", "./node_modules/moment/min/moment-with-locales.min.js", - "./node_modules/moment/locale/*.js" ], "base": "./node_modules/moment/min" }, + { + "name": "moment", + "src": [ + "./node_modules/moment/locale/*.js" + ], + "base": "./node_modules/moment/locale" + }, { "name": "ng-file-upload", "src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"], @@ -368,7 +374,7 @@ gulp.task('dependencies', function () { // add streams for node modules nodeModules.forEach(module => { stream.add( - gulp.src(module.src, + gulp.src(module.src, { base: module.base }) .pipe(gulp.dest(root + targets.lib + "/" + module.name)) ); @@ -380,9 +386,9 @@ gulp.task('dependencies', function () { .pipe(gulp.dest(root + targets.lib)) ); - //Copies all static assets into /root / assets folder + //Copies all static assets into /root / assets folder //css, fonts and image files - stream.add( + stream.add( gulp.src(sources.globs.assets) .pipe(imagemin([ imagemin.gifsicle({interlaced: true}), @@ -400,20 +406,20 @@ gulp.task('dependencies', function () { // Copies all the less files related to the preview into their folder //these are not pre-processed as preview has its own less combiler client side - stream.add( + stream.add( gulp.src("src/canvasdesigner/editors/*.less") .pipe(gulp.dest(root + targets.assets + "/less")) ); - + // Todo: check if we need these fileSize - stream.add( + stream.add( gulp.src("src/views/propertyeditors/grid/config/*.*") .pipe(gulp.dest(root + targets.views + "/propertyeditors/grid/config")) - ); - stream.add( + ); + stream.add( gulp.src("src/views/dashboard/default/*.jpg") .pipe(gulp.dest(root + targets.views + "/dashboard/default")) - ); + ); return stream; }); @@ -422,8 +428,8 @@ gulp.task('dependencies', function () { /************************** * Copies all angular JS files into their seperate umbraco.*.js file **************************/ -gulp.task('js', function () { - +gulp.task('js', function () { + //we run multiple streams, so merge them all together var stream = new MergeStream(); @@ -440,7 +446,7 @@ gulp.task('js', function () { }); gulp.task('less', function () { - + var stream = new MergeStream(); _.forEach(sources.less, function (group) { @@ -463,9 +469,9 @@ gulp.task('views', function () { gulp.src(group.files) .pipe( gulp.dest(root + targets.views + group.folder) ) ); - + }); - + return stream; }); @@ -480,13 +486,13 @@ gulp.task('watch', function () { if(group.watch !== false){ - stream.add( + stream.add( watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) { console.info(file.path + " has changed, added to: " + group.out); processJs(group.files, group.out); - + }) ); @@ -495,7 +501,7 @@ gulp.task('watch', function () { }); - stream.add( + stream.add( //watch all less files and trigger the less task watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () { gulp.run(['less']); @@ -503,13 +509,13 @@ gulp.task('watch', function () { ); //watch all views - copy single file changes - stream.add( + stream.add( watch(sources.globs.views, { interval: watchInterval }) .pipe(gulp.dest(root + targets.views)) ); //watch all app js files that will not be merged - copy single file changes - stream.add( + stream.add( watch(sources.globs.js, { interval: watchInterval }) .pipe(gulp.dest(root + targets.js)) ); @@ -554,7 +560,7 @@ gulp.task('connect:docs', function (cb) { }); gulp.task('open:docs', function (cb) { - + var options = { uri: 'http://localhost:8880/index.html' }; From ac5ee9053f7ab129daa2881fb60336a733e6bd1b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 13:32:15 +0100 Subject: [PATCH 124/278] Update & match momentjs version --- src/Umbraco.Web.UI.Client/package-lock.json | 47 +++++++++++++++------ src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 1053e0bc64..35cfa96d67 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -5464,7 +5464,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5485,12 +5486,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5505,17 +5508,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5632,7 +5638,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5644,6 +5651,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5658,6 +5666,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5665,12 +5674,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5689,6 +5700,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5769,7 +5781,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5781,6 +5794,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5866,7 +5880,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5902,6 +5917,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5921,6 +5937,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5964,12 +5981,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -9962,9 +9981,9 @@ } }, "moment": { - "version": "2.10.3", - "resolved": "http://registry.npmjs.org/moment/-/moment-2.10.3.tgz", - "integrity": "sha1-CruZ8wf2UhgwjGk17+KcV7Ggon8=" + "version": "2.10.6", + "resolved": "http://registry.npmjs.org/moment/-/moment-2.10.6.tgz", + "integrity": "sha1-bLIZZ8ecunsMpeZmRPFzZis++nc=" }, "morgan": { "version": "1.6.1", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 323ee63dd6..46baa2e169 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -30,7 +30,7 @@ "jquery-validation": "1.17.0", "jquery-validation-unobtrusive": "3.2.10", "lazyload-js": "1.0.0", - "moment": "2.10.3", + "moment": "2.10.6", "ng-file-upload": "12.2.13", "npm": "^6.4.1", "signalr": "2.3.0", From e390d28e29fd33ba19059fc51b58c7d8b188c6e1 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 14:34:02 +0200 Subject: [PATCH 125/278] fix jquery-ui --- src/Umbraco.Web.UI.Client/gulpfile.js | 4 ++-- src/Umbraco.Web.UI.Client/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index ffcec3b3be..934cf71bf8 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -303,8 +303,8 @@ gulp.task('dependencies', function () { }, { "name": "jquery-ui", - "src": ["./node_modules/jquery-ui/ui/widget.js"], - "base": "./node_modules/jquery-ui" + "src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"], + "base": "./node_modules/jquery-ui-dist" }, { "name": "jquery-validate", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 46baa2e169..963a280454 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -26,7 +26,7 @@ "font-awesome": "4.2.0", "jquery": "2.2.4", "jquery-migrate": "1.4.0", - "jquery-ui": "1.12.1", + "jquery-ui-dist": "1.12.1", "jquery-validation": "1.17.0", "jquery-validation-unobtrusive": "3.2.10", "lazyload-js": "1.0.0", From e902bb08f664fdd57ab8d0cd1a3404de21717190 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Oct 2018 13:37:20 +0100 Subject: [PATCH 126/278] Removes installing bower from the build powershell --- build/build.ps1 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/build.ps1 b/build/build.ps1 index c7deb59f1e..8548cbb1ac 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -119,10 +119,6 @@ &npm install >> $log 2>&1 Write-Output ">> $? $($error.Count)" >> $log 2>&1 - Write-Output "### install bower" >> $log 2>&1 - &npm install -g bower >> $log 2>&1 - $error.Clear() # that one fails 'cos bower is deprecated - ignore - Write-Output "### install gulp" >> $log 2>&1 &npm install -g gulp >> $log 2>&1 $error.Clear() # that one fails 'cos deprecated stuff - ignore From 140e410dcca6602feb0b445e5b300eb191c063c9 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 11 Oct 2018 14:40:18 +0200 Subject: [PATCH 127/278] Revert changes --- src/Umbraco.Web.UI/web.Template.config | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 943570376f..844855f10f 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -82,9 +82,8 @@ - - - + + From 6071be79be52ad1d75c64d1fb044f6a5680a2a32 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 14:50:34 +0200 Subject: [PATCH 128/278] set private to true in package.json. We don't need to publish on npm --- src/Umbraco.Web.UI.Client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 963a280454..21e0eafc33 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,4 +1,5 @@ { + "private": true, "scripts": { "test": "karma start test/config/karma.conf.js --singlerun", "build": "gulp" From 4f00082dd5a36993b45e23b947b691feb767aa79 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 15:06:12 +0200 Subject: [PATCH 129/278] fix angular mocks --- src/Umbraco.Web.UI.Client/gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 934cf71bf8..ed03193271 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -231,7 +231,7 @@ gulp.task('dependencies', function () { }, { "name": "angular-mocks", - "src": ["./node_modules/angular/angular-mocks.js"], + "src": ["./node_modules/angular-mocks/angular-mocks.js"], "base": "./node_modules/angular-mocks" }, { From 9800f1bd9c2ca0d003f0ffdbe87a2a14d8571fbe Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 11 Oct 2018 15:31:39 +0200 Subject: [PATCH 130/278] fix paths in karma test --- .../test/config/karma.conf.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 6a567ba5e6..07aa9935a8 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -11,19 +11,19 @@ module.exports = function (config) { files: [ //libraries - 'lib-bower/jquery/jquery.min.js', - 'lib-bower/angular/angular.js', - 'lib-bower/angular-animate/angular-animate.js', - 'lib-bower/angular-cookies/angular-cookies.js', - 'lib-bower/angular-local-storage/angular-local-storage.min.js', - 'lib-bower/angular-route/angular-route.js', - 'lib-bower/angular-sanitize/angular-sanitize.js', - 'bower_components/angular-mocks/angular-mocks.js', - 'lib-bower/angular-ui-sortable/sortable.js', - 'lib-bower/underscore/underscore-min.js', - 'lib-bower/moment/moment-with-locales.js', + 'node_modules/jquery/dist/jquery.min.js', + 'node_modules/angular/angular.js', + 'node_modules/angular-animate/angular-animate.js', + 'node_modules/angular-cookies/angular-cookies.js', + 'node_modules/angular-local-storage/dist/angular-local-storage.min.js', + 'node_modules/angular-route/angular-route.js', + 'node_modules/angular-sanitize/angular-sanitize.js', + 'node_modules/angular-mocks/angular-mocks.js', + 'node_modules/angular-ui-sortable/dist/sortable.js', + 'node_modules/underscore/underscore-min.js', + 'node_modules/moment/min/moment-with-locales.js', 'lib/umbraco/Extensions.js', - 'lib-bower/rgrove-lazyload/lazyload.js', + 'node_modules/lazyload-js/lazyload.min.js', //app bootstrap and loader 'test/config/app.unit.js', From fe906f0e81fd0a27d170127afa67e958553d7585 Mon Sep 17 00:00:00 2001 From: Mike Masey Date: Sat, 13 Oct 2018 17:59:43 +0100 Subject: [PATCH 131/278] Update lazyload.js reference on install pages. --- src/Umbraco.Web.UI.Client/src/index.html | 2 +- src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml | 2 +- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/index.html b/src/Umbraco.Web.UI.Client/src/index.html index bd354efc90..e15cf0ab62 100644 --- a/src/Umbraco.Web.UI.Client/src/index.html +++ b/src/Umbraco.Web.UI.Client/src/index.html @@ -21,7 +21,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml index 67adeaf84a..79968939ec 100644 --- a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml @@ -70,7 +70,7 @@ "umbracoBaseUrl": "@ViewBag.UmbracoBaseFolder" }; - + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 549954bfc1..01afc9f2ec 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 4924985689..38b178fcfa 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -54,7 +54,7 @@
    - + From 00afea61f0b99f3ab6ccdfde98396111d65723d2 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Sun, 14 Oct 2018 10:49:50 +0100 Subject: [PATCH 132/278] Update v8 getting started Update link so that it doesn't 404 and replace with relevant link. --- .github/V8_GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index 8cd792aa71..def923e0d0 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there. -There's [a list of tasks for v8 that haven't been completed](http://issues.umbraco.org/issues/U4?q=Due+in+version%3A+8.0.0+%23Unresolved+). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. +There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done. From fb277d80e788b9edd59413b2b9f70c46f7f441d2 Mon Sep 17 00:00:00 2001 From: Jamie Townsend Date: Sun, 14 Oct 2018 12:34:16 +0100 Subject: [PATCH 133/278] Fix for old password not showing/hiding correctly on admin users page #3069 (#3273) --- .../src/views/users/user.controller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 1569b5979b..e32d331d0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -93,8 +93,14 @@ //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password //if the user has access to the user section. So if this editor is being access, the user of course has access to this section. //the authorization check is also done on the server side when submitted. - vm.changePasswordModel.config.allowManuallyChangingPassword = !vm.user.isCurrentUser; - + + // only update the setting if not the current logged in user, otherwise leave the value as it is + // currently set in the web.config + if (!vm.user.isCurrentUser) + { + vm.changePasswordModel.config.allowManuallyChangingPassword = true; + } + vm.loading = false; }); }); From 7a474c6fa44adbb4498069a7895f3d38595d0f5f Mon Sep 17 00:00:00 2001 From: Kim Holzmann Date: Wed, 10 Oct 2018 23:06:18 +0200 Subject: [PATCH 134/278] #3219 Validation error on templates when clicking "save" Error with use of savebutton fixed, use the suppressNotification value to check state. --- .../src/views/templates/edit.controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index b23d11dea1..7017054904 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -91,15 +91,15 @@ }, function (err) { + if (suppressNotification) { + vm.page.saveButtonState = "error"; - vm.page.saveButtonState = "error"; - - localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function(data){ - var header = data[0]; - var message = data[1]; - notificationsService.error(header, message); - }); - + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + } }); }; From a3de30934d5b26cc60fe602301716f0cd82d7518 Mon Sep 17 00:00:00 2001 From: Lotte Pitcher Date: Sun, 14 Oct 2018 15:44:13 +0100 Subject: [PATCH 135/278] Re-adding the language keys for login greeting on a Sunday (greeting0) that were accidentally removed --- src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml | 1 + 18 files changed, 18 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index df94e02566..f94cb9b1c8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -537,6 +537,7 @@ Obnovte nyní pro uložení práce + Šťastnou super neděli Šťastné šílené pondělí Šťastné husté úterý Šťastnou překrásnou středu diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 2f31f2d24e..e5dfe70db7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -720,6 +720,7 @@ Forny for at gemme dine ændringer + Så er det søndag! Smil, det er mandag! Hurra, det er tirsdag! Hvilken herlig onsdag! diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index 6e26dfeec7..e41a9d2a45 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -535,6 +535,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erneuern Sie, um Ihre Arbeit zu speichern ... + Einen wunderbaren Sonntag Schönen Montag Einen großartigen Dienstag Wunderbaren Mittwoch diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index cea12955be..3073f8e3e2 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -801,6 +801,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday 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 b2b01c513e..f67748c85f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -816,6 +816,7 @@ To manage your website, simply open the Umbraco back office and start adding con Renew now to save your work + Happy super Sunday Happy manic Monday Happy tubular Tuesday Happy wonderful Wednesday diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 78fb3b7b2d..3f6808150d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -696,6 +696,7 @@ Renovar su sesión para guardar sus cambios + Feliz super domingo Feliz lunes Tremendo martes Maravilloso miércoles diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 0605474494..8d00284788 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -769,6 +769,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Renouvellez votre session maintenant pour sauvegarder votre travail + Joyeux dimanche détonnant Joyeux lundi lumineux Joyeux mardi magique Joyeux mercredi merveilleux diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml index 4b9bda319e..e53abc2b53 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ja.xml @@ -615,6 +615,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 + ハッピー スーパー日曜日 ハッピー マニアック月曜日 ハッピー最高の火曜日 ハッピー ワンダフル水曜日 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml index 62d6e5419a..65f8ff389e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nl.xml @@ -609,6 +609,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Vernieuw je sessie om je wijzigingen te behouden + Goede zondag Fijne maandag Fijne dinsdag Fijne woensdag diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 254a01c179..81cbae19c9 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -736,6 +736,7 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb Wznów sesję teraz, aby zapisać swoją pracę + Szczęśliwej super niedzieli Szczęśliwego maniakalnego poniedziałku Szczęśliwego świetnego wtorku Szczęśliwej niesamowitej środy diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 97fe7c61a8..0d40c1f3f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -912,6 +912,7 @@ © 2001 - %0%
    umbraco.com

    ]]>
    + Сегодня же выходной! Понедельник — день тяжелый... Вот уже вторник... Берегите окружающую среду diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index f16129b315..7a3380996c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -502,6 +502,7 @@ © 2001 - %0%
    umbraco.com

    ]]>
    + Happy super Sunday Happy manic Monday Happy tremendous Tuesday Happy wonderful Wednesday diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index 26c6079f7c..15a2302b5d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -529,6 +529,7 @@ To manage your website, simply open the Umbraco back office and start adding con İşinizi kaydetmek için şimdi Yenile + Pazar Pazartesi Salı Çarşamba diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml index c44672a0f9..1a0580ef07 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh.xml @@ -638,6 +638,7 @@ 已更新,继续工作。 + 星期一快乐 星期二快乐 星期三快乐 星期四快乐 diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml index e9c3fa6460..2b39999363 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/zh_tw.xml @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml index 1f3d59bc79..0a20fa07d3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nb.xml @@ -511,6 +511,7 @@ Forny innlogging for å lagre + Da er det søndag! Smil, det er mandag! Hurra, det er tirsdag! For en herlig onsdag! diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml index e9c3fa6460..2b39999363 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh_tw.xml @@ -625,6 +625,7 @@ 已更新,繼續工作。 + 超級星期天快樂 瘋狂星期一快樂 熱鬧星期二快樂 美妙星期三快樂 From 1f072a383e57cb070c6e960fdf8fb4bd42784b8d Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 15:56:22 +0100 Subject: [PATCH 136/278] ContentApp Show/Hide per user Role Making content app work show hide in manifest work for user roles. for example: "show": [ "+role/admin" ] --- .../Manifest/ManifestContentAppDefinition.cs | 19 +++++++++++++++++-- .../ContentEditing/IContentAppDefinition.cs | 7 +++++-- src/Umbraco.Web.UI.Client/package-lock.json | 19 +++++++------------ .../ContentAppDefinitionCollection.cs | 7 +++++-- .../ContentEditorContentAppDefinition.cs | 4 +++- .../ContentInfoContentAppDefinition.cs | 4 +++- .../ListViewContentAppDefinition.cs | 3 ++- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 6b8534a88f..8d1acb843a 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -6,6 +6,9 @@ using System.Text.RegularExpressions; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; + namespace Umbraco.Core.Manifest { @@ -82,9 +85,9 @@ namespace Umbraco.Core.Manifest public string[] Show { get; set; } = Array.Empty(); /// - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { - string partA, partB; + string partA, partB; switch (o) { @@ -112,6 +115,18 @@ namespace Umbraco.Core.Manifest // else iterate over each entry foreach (var rule in rules) { + if (rule.PartA == "role") + { + foreach (var group in userGroups) + { + if (rule.Matches(rule.PartA, group.Alias)) + { + ok = rule.Show; + break; + } + } + } + // if the entry does not apply, skip it if (!rule.Matches(partA, partB)) continue; diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs index 5e0c421742..2d30fc6ba9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppDefinition.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Core.Models.ContentEditing +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Models.ContentEditing { /// /// Represents a content app definition. @@ -15,6 +18,6 @@ /// the content app should be displayed or not, and return either a /// instance, or null. /// - ContentApp GetContentAppFor(object source); + ContentApp GetContentAppFor(object source, IEnumerable userGroups); } } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 35cfa96d67..14d4539d59 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -5464,8 +5464,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5880,8 +5879,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5937,7 +5935,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5981,14 +5978,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -8526,10 +8521,10 @@ "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" }, - "jquery-ui": { + "jquery-ui-dist": { "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", + "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" }, "jquery-validation": { "version": "1.17.0", diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index 7dda00e62c..bdd7455386 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -18,9 +19,11 @@ namespace Umbraco.Web.ContentApps _logger = logger; } - public IEnumerable GetContentAppsFor(object o) + public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) { - var apps = this.Select(x => x.GetContentAppFor(o)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + var currentUser = UmbracoContext.Current.Security.CurrentUser; + var roles = currentUser.Groups; + var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); var aliases = new HashSet(); List dups = null; diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs index c2d6341e87..d54d1a44d4 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs index be7a40f007..de490439ba 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppDefinition.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.ContentApps { @@ -12,7 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { switch (o) { diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs index 5c73b2fa8c..ce3ea258d1 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppDefinition.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -22,7 +23,7 @@ namespace Umbraco.Web.ContentApps _propertyEditors = propertyEditors; } - public ContentApp GetContentAppFor(object o) + public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { string contentTypeAlias, entityType; From f394af20d01d3359ee5d7ab45696edbb81a27880 Mon Sep 17 00:00:00 2001 From: Tim Payne Date: Sun, 14 Oct 2018 16:43:52 +0100 Subject: [PATCH 137/278] Update umbsections.directive.js Update the number of sections to the actual number of sections available at the present time, add a fix to the calculateWidth method so that if less items are allowed than available but there is technically enough room to show them the overflow tray still shows up. On prior version, try adding translation to your allowed user group sections and reload, you can't see the translation section in the top menu, but the overflow menu button won't show until you shrink the screen. --- .../components/application/umbsections.directive.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 7f906ddcc0..5006087ca5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,7 +12,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 7; + var maxSections = 8; //setup scope vars scope.maxSections = maxSections; @@ -46,8 +46,8 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionsWidth = 0; scope.totalSections = scope.sections.length; scope.maxSections = maxSections; - scope.overflowingSections = 0; - scope.needTray = false; + scope.overflowingSections = scope.maxSections - scope.totalSections; + scope.needTray = scope.sections.length > scope.maxSections; // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { From 3a6f2545f64698300743b3826d8da9cbfce9c671 Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 17:38:38 +0100 Subject: [PATCH 138/278] Update src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs making it case insensitive --- src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 8d1acb843a..accb696351 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -115,7 +115,7 @@ namespace Umbraco.Core.Manifest // else iterate over each entry foreach (var rule in rules) { - if (rule.PartA == "role") + if (rule.PartA.InvariantEquals("role")) { foreach (var group in userGroups) { From 64d7caee783361938641023b13c0f8dacf6e488e Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Sun, 14 Oct 2018 19:24:18 +0100 Subject: [PATCH 139/278] Update src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs Rework logic on content app permissoins. Roles should override all other. --- .../Manifest/ManifestContentAppDefinition.cs | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index accb696351..7b1d311930 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -87,7 +87,7 @@ namespace Umbraco.Core.Manifest /// public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { - string partA, partB; + string partA, partB; switch (o) { @@ -112,35 +112,49 @@ namespace Umbraco.Core.Manifest { var ok = false; - // else iterate over each entry - foreach (var rule in rules) + //if any role specific rules, deal with them first. + if (rules.Where(x => x.PartA.InvariantEquals("role")).Any()) { - if (rule.PartA.InvariantEquals("role")) + foreach (var rule in rules) { - foreach (var group in userGroups) + if (rule.PartA.InvariantEquals("role")) { - if (rule.Matches(rule.PartA, group.Alias)) + foreach (var group in userGroups) { - ok = rule.Show; - break; + if (rule.Matches(rule.PartA, group.Alias)) + { + ok = rule.Show; + break; + } } } } - - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) - continue; - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) + // if a role entry, don't let anything else override it. + if (!ok) + { return null; - - // else break - ok to display - ok = true; - break; + } } + else + { + // else iterate over each entry to check for show/hide rules. + foreach (var rule in rules) + { + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else break - ok to display + ok = true; + break; + } + } // when 'show' is specified, default is to *not* show the content app if (!ok) return null; From 575d7a3ae715c9b8756695cf76818b4f8a942207 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Oct 2018 14:44:26 +0200 Subject: [PATCH 140/278] Cleanup --- src/Umbraco.Core/Components/RelateOnCopyComponent.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs index 5356fa6e30..4ebd309e9f 100644 --- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs @@ -1,12 +1,10 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Components { - //TODO: This should just exist in the content service/repo! [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent From 7757c7060e07a9075f08e8d56237159823820930 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Oct 2018 15:48:26 +0200 Subject: [PATCH 141/278] Refactor ManifestContentAppDefinition for roles --- .../Manifest/ManifestContentAppDefinition.cs | 88 ++++++++++--------- .../Manifest/ManifestContentAppTests.cs | 77 ++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 3 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 7b1d311930..3d4b24d359 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -7,8 +7,6 @@ using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; - namespace Umbraco.Core.Manifest { @@ -106,43 +104,28 @@ namespace Umbraco.Core.Manifest } var rules = _showRules ?? (_showRules = ShowRule.Parse(Show).ToArray()); + var userGroupsList = userGroups.ToList(); - // if no 'show' is specified, then always display the content app - if (rules.Length > 0) + var okRole = false; + var hasRole = false; + var okType = false; + var hasType = false; + + foreach (var rule in rules) { - var ok = false; - - //if any role specific rules, deal with them first. - if (rules.Where(x => x.PartA.InvariantEquals("role")).Any()) + if (rule.PartA.InvariantEquals("role")) { - foreach (var rule in rules) - { - if (rule.PartA.InvariantEquals("role")) - { - foreach (var group in userGroups) - { - if (rule.Matches(rule.PartA, group.Alias)) - { - ok = rule.Show; - break; - } - } - } - } - // if a role entry, don't let anything else override it. - if (!ok) - { - return null; - } - } - else - { - // else iterate over each entry to check for show/hide rules. - foreach (var rule in rules) - { + // if roles have been ok-ed already, skip the rule + if (okRole) + continue; - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) + // remember we have role rules + hasRole = true; + + foreach (var group in userGroupsList) + { + // if the entry does not apply, skip + if (!rule.Matches("role", group.Alias)) continue; // if the entry applies, @@ -150,16 +133,41 @@ namespace Umbraco.Core.Manifest if (!rule.Show) return null; - // else break - ok to display - ok = true; + // else ok to display, remember roles are ok, break from userGroupsList + okRole = rule.Show; break; } } - // when 'show' is specified, default is to *not* show the content app - if (!ok) - return null; + else // it is a type rule + { + // if type has been ok-ed already, skip the rule + if (okType) + continue; + + // remember we have type rules + hasType = true; + + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + continue; + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + return null; + + // else ok to display, remember type rules are ok + okType = true; + } } + // if roles rules are specified but not ok, + // or if type roles are specified but not ok, + // cannot display the content app + if ((hasRole && !okRole) || (hasType && !okType)) + return null; + + // else // content app can be displayed return _app ?? (_app = new ContentApp { diff --git a/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs new file mode 100644 index 0000000000..eed0919149 --- /dev/null +++ b/src/Umbraco.Tests/Manifest/ManifestContentAppTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Manifest +{ + [TestFixture] + public class ManifestContentAppTests + { + [Test] + public void Test() + { + var contentType = Mock.Of(); + Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); + var content = Mock.Of(); + Mock.Get(content).Setup(x => x.ContentType).Returns(contentType); + + var group1 = Mock.Of(); + Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); + var group2 = Mock.Of(); + Mock.Get(group2).Setup(x => x.Alias).Returns("group2"); + + // no rule = ok + AssertDefinition(content, true, Array.Empty(), new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new [] { "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-content/type1" }, new [] { group1, group2 }); + + // when there are type rules, failing to approve the type = no app + AssertDefinition(content, false, new[] { "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/type1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new [] { group1, group2 }); + + // when there are role rules, failing to approve a role = no app + AssertDefinition(content, false, new[] { "+role/group33" }, new [] { group1, group2 }); + + // wildcards = ok + AssertDefinition(content, true, new[] { "+role/*" }, new [] { group1, group2 }); + + // explicitly enabling / disabling + AssertDefinition(content, true, new[] { "+role/group1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-role/group1" }, new [] { group1, group2 }); + + // can have multiple rule, first one that matches = end + AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new [] { group1, group2 }); + + // mixed type and role rules, both are evaluated and need to match + AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new [] { group1, group2 }); + } + + private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) + { + var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); + var app = definition.GetContentAppFor(source, groups); + if (expected) + Assert.IsNotNull(app); + else + Assert.IsNull(app); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 61ae537529..04bccc8bcb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -119,6 +119,7 @@ + From 466d782db42a0939219eb21ecb732d35f842c924 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 13:18:21 +1100 Subject: [PATCH 142/278] Fixes issue with umbsetdirtyonchange.directive --- .../directives/validation/umbsetdirtyonchange.directive.js | 7 ++++--- .../src/views/propertyeditors/grid/grid.controller.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js index c13680a037..d4e77eda05 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/umbsetdirtyonchange.directive.js @@ -7,7 +7,7 @@ var formCtrl = ctrls[0]; - if (ctrls.length > 1) { + if (ctrls.length > 1 && ctrls[1]) { //if an ngModel is supplied, assign a render function which is called when the model is changed var modelCtrl = ctrls[1]; var oldRender = modelCtrl.$render; @@ -17,8 +17,9 @@ if (oldRender) { oldRender(); } - }; - } else { + } + } + else { var initValue = attr.umbSetDirtyOnChange; attr.$observe("umbSetDirtyOnChange", function (newValue) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index b5b9910465..38e32b7451 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 @@ -888,8 +888,8 @@ angular.module("umbraco") angular.forEach($scope.availableEditors, function (value, key) { //If no translation is provided, keep using the editor name from the manifest if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { - localizationService.localize("grid_" + value.alias).then(function(value){ - value.name = value; + localizationService.localize("grid_" + value.alias).then(function(v){ + value.name = v; }); } }); From 286f7b32793a6076b89b62967e96eb757e4132ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 16:41:06 +1100 Subject: [PATCH 143/278] Gets tree syncing working, updates the GetApplicationTrees to still return a single Root since we require this everywhere for JS, fixes full screen section and makes users a full screen section (and reduces unecessary code), removes code to avoid breaking changes from v7 --- src/Umbraco.Web.UI.Client/package-lock.json | 265 +++++++++--------- .../components/tree/umbtree.directive.js | 32 ++- .../src/common/services/tree.service.js | 23 +- .../src/controllers/navigation.controller.js | 18 +- .../src/less/application/grid.less | 1 + .../src/views/components/tree/umb-tree.html | 43 ++- src/Umbraco.Web/Editors/SectionController.cs | 33 ++- .../Models/Trees/MenuItemCollection.cs | 2 + .../Models/Trees/SectionRootNode.cs | 135 +++++++-- .../Models/Trees/TreeNodeCollection.cs | 10 + .../Trees/ApplicationTreeController.cs | 50 ++-- src/Umbraco.Web/Trees/UserTreeController.cs | 33 +-- 12 files changed, 388 insertions(+), 257 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 35cfa96d67..46acc94295 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -955,7 +955,7 @@ "amqplib": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", - "integrity": "sha1-0tcxPH/6pNELzx5iUt5FkbbMe2M=", + "integrity": "sha512-l9mCs6LbydtHqRniRwYkKdqxVa6XMz3Vw1fh+2gJaaVgTM6Jk3o8RccAKWKtlhT1US5sWrFh+KKxsVUALURSIA==", "dev": true, "optional": true, "requires": { @@ -1293,7 +1293,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, "arrify": { @@ -1333,7 +1333,7 @@ "ast-types": { "version": "0.11.5", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", - "integrity": "sha1-mJCCXWYMA8KDOfMV6foKNg4x7Cg=", + "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", "dev": true, "optional": true }, @@ -1362,7 +1362,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, "asynckit": { @@ -1405,7 +1405,7 @@ }, "axios": { "version": "0.15.3", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.15.3.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", "integrity": "sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=", "dev": true, "optional": true, @@ -1415,7 +1415,7 @@ "dependencies": { "follow-redirects": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", "dev": true, "optional": true, @@ -1512,7 +1512,7 @@ }, "basic-auth": { "version": "1.0.4", - "resolved": "http://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=", "dev": true }, @@ -1736,7 +1736,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2139,7 +2139,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -2508,7 +2508,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2631,7 +2631,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2666,7 +2666,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -2743,7 +2743,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", "dev": true }, "core-util-is": { @@ -2769,7 +2769,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -2884,7 +2884,7 @@ }, "cssnano": { "version": "3.10.0", - "resolved": "http://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", "dev": true, "requires": { @@ -2979,7 +2979,7 @@ "data-uri-to-buffer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha1-dxY+qcINhkG0cH6PGKvfmnjzSDU=", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", "dev": true, "optional": true }, @@ -4006,7 +4006,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -4142,7 +4142,7 @@ "engine.io": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", - "integrity": "sha1-Dn751pDrCzVZfx1K0Comyi26OEU=", + "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", "dev": true, "requires": { "accepts": "~1.3.4", @@ -4173,7 +4173,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4190,7 +4190,7 @@ "engine.io-client": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", - "integrity": "sha1-W96xMPi5SlCsXL63JYPnpKBj3f0=", + "integrity": "sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -4209,7 +4209,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4220,7 +4220,7 @@ "engine.io-parser": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha1-TA9M/3mq7su9z96maoI8YIVAkZY=", + "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", "dev": true, "requires": { "after": "0.8.2", @@ -4316,7 +4316,7 @@ }, "es6-promise": { "version": "3.3.1", - "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", "dev": true }, @@ -4615,7 +4615,7 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "exec-buffer": { @@ -4879,7 +4879,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -5130,7 +5130,7 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, "optional": true }, @@ -5182,7 +5182,7 @@ }, "finalhandler": { "version": "0.4.0", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", "dev": true, "requires": { @@ -5194,7 +5194,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -5330,7 +5330,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -5486,14 +5486,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5514,8 +5512,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -5666,7 +5663,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5674,14 +5670,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5700,7 +5694,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5880,8 +5873,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5987,8 +5979,7 @@ "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6069,7 +6060,7 @@ "get-uri": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", - "integrity": "sha1-XHlecTJvbKEoby/IJXXNK6sq9Xg=", + "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", "dev": true, "optional": true, "requires": { @@ -6090,7 +6081,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -6107,7 +6098,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -6248,7 +6239,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -6368,7 +6359,7 @@ }, "lodash": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", "dev": true }, @@ -6503,7 +6494,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -6576,7 +6567,7 @@ }, "gulp-connect": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.0.0.tgz", "integrity": "sha1-8v3zBq6RFGg2jCKF8teC8T7dr04=", "dev": true, "requires": { @@ -6855,7 +6846,7 @@ }, "ansi-regex": { "version": "0.2.1", - "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", "dev": true }, @@ -6867,7 +6858,7 @@ }, "chalk": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", "dev": true, "requires": { @@ -6940,7 +6931,7 @@ }, "lodash": { "version": "2.4.1", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=", "dev": true }, @@ -7030,13 +7021,13 @@ }, "minimist": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", "dev": true }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -7048,7 +7039,7 @@ }, "strip-ansi": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", "dev": true, "requires": { @@ -7232,7 +7223,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -7421,7 +7412,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { "isarray": "2.0.1" @@ -7556,7 +7547,7 @@ }, "http-errors": { "version": "1.3.1", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", "dev": true, "requires": { @@ -7573,7 +7564,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -7584,7 +7575,7 @@ "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha1-5IIb7vWyFCogJr1zkm/lN2McVAU=", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { "agent-base": "4", @@ -7594,7 +7585,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -7640,7 +7631,7 @@ "https-proxy-agent": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha1-UVUpcPoE1yPgTFbQQXjD+SWSu8A=", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "dev": true, "requires": { "agent-base": "^4.1.0", @@ -7666,7 +7657,7 @@ }, "iconv-lite": { "version": "0.4.11", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=", "dev": true }, @@ -8090,7 +8081,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -8234,7 +8225,7 @@ "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha1-ezUbjo7dTTmV1NBmaA5mTZRpaCQ=", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", "dev": true, "optional": true }, @@ -8526,10 +8517,10 @@ "resolved": "https://registry.npmjs.org/jquery-migrate/-/jquery-migrate-1.4.0.tgz", "integrity": "sha1-4AKOSDHMFH2PIvOCBRbr+5dReaU=" }, - "jquery-ui": { + "jquery-ui-dist": { "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", + "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" }, "jquery-validation": { "version": "1.17.0", @@ -8630,7 +8621,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -8818,7 +8809,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -8831,7 +8822,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -8849,7 +8840,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "range-parser": { @@ -8861,7 +8852,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "requires": { "bytes": "3.0.0", @@ -8873,7 +8864,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "utils-merge": { @@ -9043,7 +9034,7 @@ "dependencies": { "iconv-lite": { "version": "0.4.15", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", "dev": true } @@ -9079,7 +9070,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -9553,7 +9544,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "optional": true, @@ -9568,7 +9559,7 @@ }, "request": { "version": "2.75.0", - "resolved": "http://registry.npmjs.org/request/-/request-2.75.0.tgz", + "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", "integrity": "sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM=", "dev": true, "optional": true, @@ -9690,7 +9681,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "optional": true, "requires": { @@ -9766,7 +9757,7 @@ }, "marked": { "version": "0.3.2", - "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.2.tgz", "integrity": "sha1-AV2xWIZEOPJKZL3WGgQotBhwbQk=", "dev": true }, @@ -9814,7 +9805,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -9946,7 +9937,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -9973,7 +9964,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -9987,7 +9978,7 @@ }, "morgan": { "version": "1.6.1", - "resolved": "http://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", "dev": true, "requires": { @@ -10000,7 +9991,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -13328,7 +13319,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "optional": true, @@ -13342,7 +13333,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "optional": true, "requires": { @@ -13359,7 +13350,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "optional": true, "requires": { @@ -13374,7 +13365,7 @@ "pac-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha1-auoweH2wqJFwTet4AKcip2FabyY=", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", "dev": true, "optional": true, "requires": { @@ -13585,7 +13576,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -13725,7 +13716,7 @@ }, "postcss-calc": { "version": "5.3.1", - "resolved": "http://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", "dev": true, "requires": { @@ -13757,7 +13748,7 @@ }, "postcss-discard-comments": { "version": "2.0.4", - "resolved": "http://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", "dev": true, "requires": { @@ -13775,7 +13766,7 @@ }, "postcss-discard-empty": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", "dev": true, "requires": { @@ -13784,7 +13775,7 @@ }, "postcss-discard-overridden": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", "dev": true, "requires": { @@ -13793,7 +13784,7 @@ }, "postcss-discard-unused": { "version": "2.2.3", - "resolved": "http://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", "dev": true, "requires": { @@ -13844,7 +13835,7 @@ }, "postcss-merge-idents": { "version": "2.1.7", - "resolved": "http://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", "dev": true, "requires": { @@ -13883,7 +13874,7 @@ }, "postcss-minify-font-values": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", "dev": true, "requires": { @@ -13894,7 +13885,7 @@ }, "postcss-minify-gradients": { "version": "1.0.5", - "resolved": "http://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", "dev": true, "requires": { @@ -13904,7 +13895,7 @@ }, "postcss-minify-params": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", "dev": true, "requires": { @@ -13916,7 +13907,7 @@ }, "postcss-minify-selectors": { "version": "2.1.1", - "resolved": "http://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", "dev": true, "requires": { @@ -13928,7 +13919,7 @@ }, "postcss-normalize-charset": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", "dev": true, "requires": { @@ -13937,7 +13928,7 @@ }, "postcss-normalize-url": { "version": "3.0.8", - "resolved": "http://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", "dev": true, "requires": { @@ -13959,7 +13950,7 @@ }, "postcss-reduce-idents": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", "dev": true, "requires": { @@ -13969,7 +13960,7 @@ }, "postcss-reduce-initial": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", "dev": true, "requires": { @@ -13978,7 +13969,7 @@ }, "postcss-reduce-transforms": { "version": "1.0.4", - "resolved": "http://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", "dev": true, "requires": { @@ -14000,7 +13991,7 @@ }, "postcss-svgo": { "version": "2.1.6", - "resolved": "http://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", "dev": true, "requires": { @@ -14012,7 +14003,7 @@ }, "postcss-unique-selectors": { "version": "2.0.2", - "resolved": "http://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", "dev": true, "requires": { @@ -14029,7 +14020,7 @@ }, "postcss-zindex": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", "dev": true, "requires": { @@ -14136,7 +14127,7 @@ "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "optional": true, "requires": { @@ -14189,7 +14180,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -14258,7 +14249,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true } @@ -14349,7 +14340,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -14430,7 +14421,7 @@ "redis": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha1-ICKI4/WMSfYHnZevehDhMDrhSwI=", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", "dev": true, "optional": true, "requires": { @@ -14442,7 +14433,7 @@ "redis-commands": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha1-RJWIlBTx6IYmEYCxRC5ylWAtg6I=", + "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==", "dev": true, "optional": true }, @@ -14455,7 +14446,7 @@ }, "reduce-css-calc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", "dev": true, "requires": { @@ -14667,7 +14658,7 @@ "requestretry": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz", - "integrity": "sha1-IT7BAG7rdQ6LjOVBdig9FajVXZQ=", + "integrity": "sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==", "dev": true, "optional": true, "requires": { @@ -14887,7 +14878,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "sax": { @@ -14964,7 +14955,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -15034,7 +15025,7 @@ "dependencies": { "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -15092,7 +15083,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "shebang-command": { @@ -15330,7 +15321,7 @@ "socket.io-parser": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", - "integrity": "sha1-7S2l7nnxCVUDbj2kE7/X8eTYbI4=", + "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -15342,7 +15333,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -15634,7 +15625,7 @@ "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", + "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15666,7 +15657,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15682,7 +15673,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15737,7 +15728,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -15983,7 +15974,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -16023,7 +16014,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -16176,7 +16167,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -16191,7 +16182,7 @@ }, "iconv-lite": { "version": "0.4.13", - "resolved": "http://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", "dev": true }, @@ -16217,7 +16208,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -16432,7 +16423,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, "unc-path-regex": { @@ -16694,7 +16685,7 @@ "uws": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", - "integrity": "sha1-+sg4a+/DOno3BcvVjcR7Qwyk3ZU=", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", "dev": true, "optional": true }, @@ -16937,7 +16928,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -17076,7 +17067,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { "async-limiter": "~1.0.0", @@ -17112,7 +17103,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 251683edc8..3212690bbb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -174,6 +174,29 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } + /** This will check the section tree loaded and return all actual root nodes based on a tree type (non group nodes, non section groups) */ + function getTreeRootNodes() { + var roots; + if ($scope.tree.root.containsGroups) { + //all children in this case are group nodes, so we want the children of these children + roots = _.reduce( + //get the array of array of children + _.map($scope.tree.root.children, function (n) { + return n.children + }), function (m, p) { + //combine the arrays to one array + return m.concat(p) + }); + } + else { + roots = [$scope.tree.root].concat($scope.tree.root.children); + } + + return _.filter(roots, function (node) { + return node && node.metaData && node.metaData.treeAlias; + }); + } + //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node function loadActiveTree(treeAlias) { @@ -189,12 +212,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use return $scope.activeTree; } - var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children); - $scope.activeTree = _.find(childrenAndSelf, function (node) { - if (node && node.metaData && node.metaData.treeAlias) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - } - return false; + var treeRoots = getTreeRootNodes(); + $scope.activeTree = _.find(treeRoots, function (node) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); }); if (!$scope.activeTree) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index a5bbf2f886..3b605453c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -542,11 +542,17 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS root: data }; - for (var i = 0; i < result.root.length; i++) { - var group = result.root[i]; + //format the root + self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(group, group.children, args.section); + //if this is a root that contains group nodes, we need to format those manually too + if (result.root.containsGroups) { + for (var i = 0; i < result.root.children.length; i++) { + var group = result.root.children[i]; + + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(group, group.children, args.section); + } } //cache this result if a cache key is specified - generally a cache key should ONLY @@ -700,12 +706,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!angular.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; } - //all root nodes have metadata key 'treeAlias' + var reversePath = []; var current = node; while (current != null) { reversePath.push(current.id); - if (current.metaData && current.metaData["treeAlias"]) { + + //all tree root nodes (non group, not section root) have a treeAlias so exit if that is the case + //or exit if we cannot traverse further up + if ((current.metaData && current.metaData["treeAlias"]) || !current.parent) { current = null; } else { @@ -714,7 +723,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } return reversePath.reverse(); }, - + syncTree: function(args) { if (!args) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index dcc17612b3..2912755ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -184,19 +184,17 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //Listen for section state changes evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.value.root.length > 0) - { - for (var i = 0; i < args.value.root.length; i++) { - var group = args.value.root[i]; + if (args.key === "currentRootNode") { - if(group.metaData.containsTrees === false){ - $rootScope.emptySection = true; - } + //if the changed state is the currentRootNode, determine if this is a full screen app + if (args.value.root && args.value.root.containsTrees === false) { + $rootScope.emptySection = true; + } + else { + $rootScope.emptySection = false; } } - else { - $rootScope.emptySection = false; - } + })); //Listen for section state changes diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 7ed2abc898..a7b4bd0011 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -167,6 +167,7 @@ body.umb-drawer-is-visible #mainwrapper{ @media (min-width: 1101px) { #contentwrapper, #umb-notifications-wrapper {left: 360px;} #speechbubble {left: 360px;} + .emptySection #contentwrapper {left:0px;} } //empty section modification diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html index 3713312968..a8251cffe6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree.html @@ -1,6 +1,30 @@ \ No newline at end of file + diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs index 7c3eb94059..dc7ddb7201 100644 --- a/src/Umbraco.Web/Editors/SectionController.cs +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Web.Trees; using Section = Umbraco.Web.Models.ContentEditing.Section; +using Umbraco.Web.Models.Trees; namespace Umbraco.Web.Editors { @@ -30,6 +31,7 @@ namespace Umbraco.Web.Editors // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that // since tree's by nature are controllers and require request contextual data - and then we have to // remember to inject properties - nasty indeed + // fixme - this controller could/should be able to be created from the container and/or from webapi's IHttpControllerTypeResolver var appTreeController = new ApplicationTreeController(); Current.Container.InjectProperties(appTreeController); appTreeController.ControllerContext = ControllerContext; @@ -49,20 +51,35 @@ namespace Umbraco.Web.Editors if (hasDashboards == false) { //get the first tree in the section and get it's root node route path - var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - - //Root node trees are now in collection - var firstTree = sectionTrees.FirstOrDefault(); - - section.RoutePath = firstTree.IsContainer == false || firstTree.Children.Count == 0 - ? firstTree.RoutePath - : firstTree.Children[0].RoutePath; + var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; + section.RoutePath = GetRoutePathForFirstTree(sectionRoot); } } return sectionModels; } + /// + /// Returns the first non root/group node's route path + /// + /// + /// + private string GetRoutePathForFirstTree(TreeRootNode rootNode) + { + if (!rootNode.IsContainer || !rootNode.ContainsTrees) + return rootNode.RoutePath; + + foreach(var node in rootNode.Children) + { + if (node is TreeRootNode groupRoot) + return GetRoutePathForFirstTree(groupRoot);//recurse to get the first tree in the group + else + return node.RoutePath; + } + + return string.Empty; + } + /// /// Returns all the sections that the user has access to /// diff --git a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs index 05796015e9..e1fd2218e0 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItemCollection.cs @@ -9,6 +9,8 @@ namespace Umbraco.Web.Models.Trees [DataContract(Name = "menuItems", Namespace = "")] public class MenuItemCollection { + public static MenuItemCollection Empty => new MenuItemCollection(); + private readonly MenuItemList _menuItems = new MenuItemList(); public MenuItemCollection() diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 34f8e2d351..4265cbaa7f 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -1,55 +1,154 @@ -using System.Linq; +using System.Globalization; +using System.Linq; using System.Runtime.Serialization; -using Umbraco.Core; namespace Umbraco.Web.Models.Trees { /// - /// A special tree node that represents the section root node for any section. + /// A tree node that represents various types of root nodes /// /// + /// + /// A represents: + /// * The root node for a section containing a single tree + /// * The root node for a section containing multiple sub-trees + /// * The root node for a section containing groups of multiple sub-trees + /// * The group node in a section containing groups of multiple sub-trees + /// + /// /// This is required to return the tree data for a given section. Some sections may only contain one tree which means it's section /// root should also display a menu, whereas other sections have multiple trees and the section root shouldn't display a menu. - /// - /// The section root also contains an explicit collection of children. + /// + /// + /// The root node also contains an explicit collection of children. + /// /// [DataContract(Name = "node", Namespace = "")] - public sealed class SectionRootNode : TreeNode + public sealed class TreeRootNode : TreeNode { - public static SectionRootNode CreateMultiTreeSectionRoot(string nodeId, TreeNodeCollection children) - { - var sectionRoot = new SectionRootNode(nodeId, "", "") - { - IsContainer = true, - Children = children - }; + private static readonly string RootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture); + private bool _isGroup; - //some metadata as to whether or not this section contains any trees - sectionRoot.AdditionalData["containsTrees"] = children.Any(); + /// + /// Creates a group node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupNode(TreeNodeCollection children) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsGroup = true, + Children = children + }; return sectionRoot; } - public static SectionRootNode CreateSingleTreeSectionRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + /// + /// Creates a section root node for grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateGroupedMultiTreeRoot(TreeNodeCollection children) { - return new SectionRootNode(nodeId, getChildNodesUrl, menuUrl) + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children, + ContainsGroups = true + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for non-grouped multiple trees + /// + /// + /// + public static TreeRootNode CreateMultiTreeRoot(TreeNodeCollection children) + { + var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) + { + IsContainer = true, + Children = children + }; + + return sectionRoot; + } + + /// + /// Creates a section root node for a section with a single tree + /// + /// + /// + /// + /// + /// + /// + public static TreeRootNode CreateSingleTreeRoot(string nodeId, string getChildNodesUrl, string menuUrl, string title, TreeNodeCollection children) + { + return new TreeRootNode(nodeId, getChildNodesUrl, menuUrl) { Children = children, Name = title }; } - private SectionRootNode(string nodeId, string getChildNodesUrl, string menuUrl) + /// + /// Private constructor + /// + /// + /// + /// + private TreeRootNode(string nodeId, string getChildNodesUrl, string menuUrl) : base(nodeId, null, getChildNodesUrl, menuUrl) { //default to false IsContainer = false; } + /// + /// Will be true if this is a multi-tree section root node (i.e. contains other trees) + /// [DataMember(Name = "isContainer")] public bool IsContainer { get; private set; } + /// + /// True if this is a group root node + /// + [DataMember(Name = "isGroup")] + public bool IsGroup + { + get => _isGroup; + private set + { + //if a group is true then it is also a container + _isGroup = value; + IsContainer = true; + } + } + + /// + /// True if this root node contains group root nodes + /// + [DataMember(Name = "containsGroups")] + public bool ContainsGroups { get; private set; } + + /// + /// The node's children collection + /// [DataMember(Name = "children")] public TreeNodeCollection Children { get; private set; } + + /// + /// Returns true if there are any children + /// + /// + /// This is used in the UI to configure a full screen section/app + /// + [DataMember(Name = "containsTrees")] + public bool ContainsTrees => Children.Count > 0; } } diff --git a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs index 39403f144a..48e9b46dbe 100644 --- a/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Web/Models/Trees/TreeNodeCollection.cs @@ -6,5 +6,15 @@ namespace Umbraco.Web.Models.Trees [CollectionDataContract(Name = "nodes", Namespace = "")] public sealed class TreeNodeCollection : List { + public static TreeNodeCollection Empty => new TreeNodeCollection(); + + public TreeNodeCollection() + { + } + + public TreeNodeCollection(IEnumerable nodes) + : base(nodes) + { + } } } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7e73f44267..53c8d26bc2 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -44,16 +44,12 @@ namespace Umbraco.Web.Trees /// An optional bool (defaults to true), if set to false it will also load uninitialized trees /// [HttpQueryStringFilter("queryStrings")] - public async Task> GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) + public async Task GetApplicationTrees(string application, string tree, FormDataCollection queryStrings, bool onlyInitialized = true) { application = application.CleanForXss(); - var rootNodeGroups = new List(); - if (string.IsNullOrEmpty(application)) throw new HttpResponseException(HttpStatusCode.NotFound); - var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - //find all tree definitions that have the current application alias var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray(); @@ -71,11 +67,10 @@ namespace Umbraco.Web.Trees queryStrings, application); - //this will be null if it cannot convert to ta single root section + //this will be null if it cannot convert to a single root section if (result != null) { - rootNodeGroups.Add(result); - return rootNodeGroups; + return result; } } @@ -93,17 +88,18 @@ namespace Umbraco.Web.Trees //Don't apply fancy grouping logic futher down, if we only have one group of items var hasGroups = CoreTrees.Value.Count > 0; - if (hasGroups == false) + if (!hasGroups) { - var multiTree = SectionRootNode.CreateMultiTreeSectionRoot(rootId, collection); + var multiTree = TreeRootNode.CreateMultiTreeRoot(collection); multiTree.Name = Services.TextService.Localize("sections/" + application); - rootNodeGroups.Add(multiTree); - return rootNodeGroups; + return multiTree; } - + + var rootNodeGroups = new List(); + //Group trees by [CoreTree] attribute with a TreeGroup property - foreach(var treeSectionGroup in CoreTrees.Value) + foreach (var treeSectionGroup in CoreTrees.Value) { var treeGroupName = treeSectionGroup.Key; @@ -137,14 +133,14 @@ namespace Umbraco.Web.Trees if (groupNodeCollection.Count > 0) { - var groupRoot = SectionRootNode.CreateMultiTreeSectionRoot(rootId, groupNodeCollection); + var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); rootNodeGroups.Add(groupRoot); } } - return rootNodeGroups.OrderBy(x => x.Name); + return TreeRootNode.CreateGroupedMultiTreeRoot(new TreeNodeCollection(rootNodeGroups.OrderBy(x => x.Name))); } /// @@ -188,7 +184,7 @@ namespace Umbraco.Web.Trees /// /// /// - private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) + private async Task GetRootForSingleAppTree(ApplicationTree configTree, string id, FormDataCollection queryStrings, string application) { var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture); if (configTree == null) throw new ArgumentNullException(nameof(configTree)); @@ -202,26 +198,16 @@ namespace Umbraco.Web.Trees throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias); } - //if the root node has a route path, we cannot create a single root section because by specifying the route path this would - //override the dashboard route and that means there can be no dashboard for that section which is a breaking change. - if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false - && rootNode.Result.RoutePath != "#" - && rootNode.Result.RoutePath != application) - { - //null indicates this cannot be converted - return null; - } - - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( rootId, rootNode.Result.ChildNodesUrl, rootNode.Result.MenuUrl, rootNode.Result.Name, byControllerAttempt.Result); - //This can't be done currently because the root will default to routing to a dashboard and if we disable dashboards for a section - //that is really considered a breaking change. See above. - //sectionRoot.RoutePath = rootNode.Result.RoutePath; + //assign the route path based on the root node, this means it will route there when the section is navigated to + //and no dashboards will be available for this section + sectionRoot.RoutePath = rootNode.Result.RoutePath; foreach (var d in rootNode.Result.AdditionalData) { @@ -233,7 +219,7 @@ namespace Umbraco.Web.Trees var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); if (legacyAttempt.Success) { - var sectionRoot = SectionRootNode.CreateSingleTreeSectionRoot( + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( rootId, "", //TODO: I think we'll need this in this situation! Url.GetUmbracoApiService("GetMenu", rootId) diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index db1bca0234..e6bd53ddf8 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -33,39 +33,14 @@ namespace Umbraco.Web.Trees protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { - var nodes = new TreeNodeCollection(); - return nodes; + //full screen app without tree nodes + return TreeNodeCollection.Empty; } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - //Create User - var createMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("actions/create")); - createMenuItem.Icon = "add"; - createMenuItem.NavigateToRoute("users/users/overview?subview=users&create=true"); - menu.Items.Add(createMenuItem); - - //This is the same setting used in the global JS for 'showUserInvite' - if (EmailSender.CanSendRequiredEmail) - { - //Invite User (Action import closest type of action to an invite user) - var inviteMenuItem = menu.Items.CreateMenuItem(Services.TextService.Localize("user/invite")); - inviteMenuItem.Icon = "message-unopened"; - inviteMenuItem.NavigateToRoute("users/users/overview?subview=users&invite=true"); - - menu.Items.Add(inviteMenuItem); - } - - return menu; - } - - //There is no context menu options for editing a specific user - //Also we no longer list each user in the tree & in theory never hit this - return menu; + //doesn't have a menu, this is a full screen app without tree nodes + return MenuItemCollection.Empty; } } } From 831657537f92236e076d23be1db7bcb8e7e9eb2d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 16:56:28 +1100 Subject: [PATCH 144/278] tree webforms cleanup --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 13 - src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs | 21 - .../Umbraco/TreeInit.aspx.designer.cs | 69 -- .../Umbraco/controls/PasswordChanger.ascx.cs | 28 - .../controls/PasswordChanger.ascx.designer.cs | 69 -- .../controls/Tree/CustomTreeService.asmx | 1 - .../Umbraco/controls/Tree/TreeControl.ascx | 57 -- .../Umbraco/controls/passwordChanger.ascx | 133 ---- .../Umbraco/dialogs/create.aspx | 67 -- src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm | 9 - .../Umbraco/dialogs/treePicker.aspx | 22 - src/Umbraco.Web.UI/Umbraco/treeInit.aspx | 47 -- src/Umbraco.Web/Umbraco.Web.csproj | 22 - .../controls/Tree/CustomTreeControl.cs | 123 ---- .../controls/Tree/CustomTreeService.asmx | 1 - .../controls/Tree/CustomTreeService.cs | 154 ----- .../umbraco/controls/Tree/JTreeContextMenu.cs | 59 -- .../controls/Tree/JTreeContextMenuItem.cs | 82 --- .../umbraco/controls/Tree/NodeInfo.cs | 32 - .../umbraco/controls/Tree/TreeControl.ascx.cs | 593 ------------------ .../umbraco/dialogs/create.aspx.cs | 160 ----- .../umbraco/dialogs/empty.htm | 9 - .../umbraco/dialogs/treePicker.aspx.cs | 47 -- 23 files changed, 1818 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx delete mode 100644 src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/treeInit.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2fc8cfc8bc..de52021220 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -198,13 +198,6 @@ umbracoPage.Master - - treeInit.aspx - ASPXCodeBehind - - - treeInit.aspx - @@ -325,7 +318,6 @@ - @@ -340,7 +332,6 @@ - Designer @@ -352,16 +343,13 @@ - - - Designer @@ -445,7 +433,6 @@ Form - diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs deleted file mode 100644 index f82be2b80f..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.UI.Umbraco -{ - [Obsolete("Used the TreeControl control instead. This does however get used by the TreeService when requesting the tree init url.")] - public partial class TreeInit : Pages.UmbracoEnsuredPage - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); - DataBind(); - } - - protected TreeService TreeParams { get; private set; } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs deleted file mode 100644 index 7009fe3289..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/TreeInit.aspx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco { - - - public partial class TreeInit { - - /// - /// Head1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlHead Head1; - - /// - /// ClientLoader control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.JavaScript.UmbracoClientDependencyLoader ClientLoader; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - - /// - /// ScriptManager1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.ScriptManager ScriptManager1; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs deleted file mode 100644 index a15ebbf2c7..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration.Provider; -using System.Linq; -using System.Web; -using System.Web.Security; - -namespace Umbraco.Web.UI.Umbraco.Controls -{ - public partial class PasswordChanger : global::umbraco.controls.passwordChanger - { - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //always reset the control vals - ResetPasswordCheckBox.Checked = false; - umbPasswordChanger_passwordCurrent.Text = null; - umbPasswordChanger_passwordNew.Text = null; - umbPasswordChanger_passwordNewConfirm.Text = null; - //reset the flag always - IsChangingPasswordField.Value = "false"; - this.DataBind(); - } - - - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs b/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs deleted file mode 100644 index 94193a1450..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/PasswordChanger.ascx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Controls { - - - public partial class PasswordChanger { - - /// - /// ResetPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder ResetPlaceHolder; - - /// - /// CurrentPasswordPlaceHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder CurrentPasswordPlaceHolder; - - /// - /// CurrentPasswordValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator CurrentPasswordValidator; - - /// - /// NewPasswordRequiredValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RequiredFieldValidator NewPasswordRequiredValidator; - - /// - /// NewPasswordLengthValidator control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RegularExpressionValidator NewPasswordLengthValidator; - - /// - /// Div1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl Div1; - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx deleted file mode 100644 index 2ddab055f6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/CustomTreeService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService language="C#" class="umbraco.controls.Tree.CustomTreeService" %> \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx b/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx deleted file mode 100644 index 9488fb0643..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/Tree/TreeControl.ascx +++ /dev/null @@ -1,57 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="true" Inherits="umbraco.controls.Tree.TreeControl" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="Umbraco.Web" %> - - - - - - - - - - - -
    -
    -
    -
    \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx b/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx deleted file mode 100644 index 9f94a70c45..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/controls/passwordChanger.ascx +++ /dev/null @@ -1,133 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="True" CodeBehind="passwordChanger.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Controls.PasswordChanger" %> - - - -<%= Services.TextService.Localize("user/changePassword") %>
    - - - -
    -

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

    -
    diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx deleted file mode 100644 index e67b5b0b32..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/create.aspx +++ /dev/null @@ -1,67 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" - AutoEventWireup="True" Inherits="umbraco.dialogs.create" %> - -<%@ Import Namespace="Umbraco.Web" %> -<%@ Register Src="../controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - " /> - - - - - - -
    - " onclick="onNodeSelectionConfirmed();" - disabled="true" style="width: 100px" /> -   - <%= Services.TextService.Localize("or") %>  - <%=Services.TextService.Localize("cancel")%> -
    -
    - - - - - -
    diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm b/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm deleted file mode 100644 index 4df7696ed6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/empty.htm +++ /dev/null @@ -1,9 +0,0 @@ - - - - Umbraco - empty document - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx deleted file mode 100644 index 9add7d07f2..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/treePicker.aspx +++ /dev/null @@ -1,22 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" CodeBehind="treePicker.aspx.cs" - AutoEventWireup="True" Inherits="umbraco.dialogs.treePicker" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="umb2" TagName="Tree" Src="../controls/Tree/TreeControl.ascx" %> - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx b/src/Umbraco.Web.UI/Umbraco/treeInit.aspx deleted file mode 100644 index aca6ecc07a..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/treeInit.aspx +++ /dev/null @@ -1,47 +0,0 @@ -<%@ Page Language="c#" CodeBehind="TreeInit.aspx.cs" AutoEventWireup="True" Inherits="Umbraco.Web.UI.Umbraco.TreeInit" %> -<%@ Register Src="controls/Tree/TreeControl.ascx" TagName="TreeControl" TagPrefix="umbraco" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.JavaScript" Assembly="Umbraco.Web" %> - - - - - - - - - - - - - - - - - -
    - - -
    - -
    -
    - - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 65be911ced..37f4868bc1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1034,18 +1034,12 @@ - - ASPXCodeBehind - ASPXCodeBehind ASPXCodeBehind - - ASPXCodeBehind - @@ -1205,9 +1199,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1265,17 +1256,6 @@ - - ASPXCodeBehind - - - Component - - - - - Code - @@ -1421,7 +1401,6 @@ - ASPXCodeBehind @@ -1430,7 +1409,6 @@ - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs deleted file mode 100644 index 12b056fedd..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeControl.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.IO; -using System.Web.UI; -using System.Web.UI.HtmlControls; -using ClientDependency.Core; -using umbraco.controls.Tree; -using Umbraco.Core.IO; -using Umbraco.Core.Services; - -namespace umbraco.controls.Tree -{ - /// - /// A custom tree control that uses a custom web service to return the initial node, this is required - /// due to a bug that exists in Umbraco 4.5.1 tree control/web service. - /// - /// - /// Since we're inheriting from a UserControl and all of the ClientDependency registrations are done inline, we need - /// to re-register the ClientDependencies. - /// - [ClientDependency(11, ClientDependencyType.Javascript, "Tree/jquery.tree.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/UmbracoContext.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/jquery.tree.contextmenu.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/jquery.tree.checkbox.js", "UmbracoClient")] - [ClientDependency(12, ClientDependencyType.Javascript, "Tree/NodeDefinition.js", "UmbracoClient")] - [ClientDependency(13, ClientDependencyType.Javascript, "Tree/UmbracoTree.js", "UmbracoClient")] - public class CustomTreeControl : TreeControl - { - /// - /// Ensure child controls are created on init - /// - /// - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - - this.EnsureChildControls(); - } - - /// - /// Create the child controls - /// - protected override void CreateChildControls() - { - base.CreateChildControls(); - - TreeContainer = new HtmlGenericControl(); - TreeContainer.TagName = "div"; - TreeContainer.ID = "TreeContainer"; - - this.Controls.Add(TreeContainer); - } - - /// - /// Adds the internal markup to the TreeContainer control - /// - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - //add the internal markup to the TreeContainer - /*
    */ - TreeContainer.Controls.Add(new LiteralControl(@"
    ")); - } - - /// - /// Render out the correct markup for the tree - /// - /// - /// Since we're inheriting from a UserControl, we need to render out the markup manually - /// - /// - protected override void Render(System.Web.UI.HtmlTextWriter writer) - { - //You'll notice that we're replacing the 'serviceUrl' parameter with our own custom web service! - - writer.Write(@" -"); - - //render the controls - TreeContainer.RenderControl(writer); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx deleted file mode 100644 index 2ddab055f6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.asmx +++ /dev/null @@ -1 +0,0 @@ -<%@ WebService language="C#" class="umbraco.controls.Tree.CustomTreeService" %> \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs deleted file mode 100644 index 6d91847888..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/CustomTreeService.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using Umbraco.Core.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Script.Services; -using System.Web.Services; -using System.Web.UI; -using umbraco; -using umbraco.cms.businesslogic; -using umbraco.cms.presentation.Trees; -using umbraco.controls.Tree; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Security; -using Umbraco.Web.WebServices; - -namespace umbraco.controls.Tree -{ - /// - /// Client side ajax utlities for the tree - /// - [ScriptService] - [WebService] - public class CustomTreeService : UmbracoWebService - { - /// - /// Returns some info about the node such as path and id - /// - /// - /// - [WebMethod] - [ScriptMethod(ResponseFormat = ResponseFormat.Json)] - public NodeInfo GetNodeInfo(int id) - { - Authorize(); - - //var node = new CMSNode(id); - var node = Services.EntityService.Get(id); - return new NodeInfo() - { - Id = node.Id, - Path = node.Path, - PathAsNames = string.Join("->", - GetPathNames(node.Path.Split(',') - .Select(x => int.Parse(x)) - .ToArray())) - }; - } - - /// - /// returns the node names for each id passed in - /// - /// - /// - private string[] GetPathNames(int[] ids) - { - return ids - .Where(x => x != -1) - //.Select(x => new CMSNode(x).Text).ToArray(); - .Select(x => Services.EntityService.Get(x).Name).ToArray(); - } - - /// - /// Returns a key/value object with: json, app, js as the keys - /// - /// - [WebMethod] - [ScriptMethod(ResponseFormat = ResponseFormat.Json)] - public Dictionary GetInitAppTreeData(string app, string treeType, bool showContextMenu, bool isDialog, TreeDialogModes dialogMode, string functionToCall, string nodeKey) - { - Authorize(); - - var treeCtl = new TreeControl() - { - ShowContextMenu = showContextMenu, - IsDialog = isDialog, - DialogMode = dialogMode, - App = app, - TreeType = string.IsNullOrEmpty(treeType) ? "" : treeType, //don't set the tree type unless explicitly set - NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey, - //StartNodeID = -1, //TODO: set this based on parameters! - FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall - }; - - var returnVal = new Dictionary(); - - if (string.IsNullOrEmpty(treeType)) - { - //if there's not tree type specified, then render out the tree as per normal with the normal - //way of doing things - returnVal.Add("json", treeCtl.GetJSONInitNode()); - } - else - { - //since 4.5.1 has a bug in it, it ignores if the treeType is specified and will always only render - //the whole APP not just a specific tree. - //this is a work around for this bug until it is fixed (which should be fixed in 4.5.2 - - //get the tree that we need to render - var tree = TreeDefinitionCollection.Instance.FindTree(treeType).CreateInstance(); - tree.ShowContextMenu = showContextMenu; - tree.IsDialog = isDialog; - tree.DialogMode = dialogMode; - tree.NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey; - tree.FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall; - - //now render it's start node - var xTree = new XmlTree(); - - //we're going to hijack the node name here to make it say content/media - var node = tree.RootNode; - if (node.Text.Equals("[FilteredContentTree]")) node.Text = Services.TextService.Localize("content"); - else if (node.Text.Equals("[FilteredMediaTree]")) node.Text = Services.TextService.Localize("media"); - xTree.Add(node); - - returnVal.Add("json", xTree.ToString()); - } - - returnVal.Add("app", app); - returnVal.Add("js", treeCtl.JSCurrApp); - - return returnVal; - } - - internal void Authorize() - { - if (ValidateCurrentUser() == false) - throw new Exception("Client authorization failed. User is not logged in"); - } - - - /// - /// Validates the currently logged in user and ensures they are not timed out - /// - /// - private bool ValidateCurrentUser() - { - var identity = Context.GetCurrentIdentity( - //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! - // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is - // a back office user logged in. This can cause problems becaues the identity is changing mid - // request. For example: http://issues.umbraco.org/issue/U4-4010 - HttpContext.Current.CurrentHandler is Page); - - if (identity != null) - { - return true; - } - return false; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs deleted file mode 100644 index 726936f570..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Script.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; -using Action = Umbraco.Web._Legacy.Actions.Action; - -namespace umbraco.controls.Tree -{ - internal class JTreeContextMenu - { - public string RenderJSONMenu() - { - - JavaScriptSerializer jSSerializer = new JavaScriptSerializer(); - - jSSerializer.RegisterConverters(new List() - { - new JTreeContextMenuItem() - }); - - List allActions = new List(); - foreach (var a in Current.Actions) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (!string.IsNullOrEmpty(a.Alias) && (!string.IsNullOrEmpty(a.JsFunctionName) || !string.IsNullOrEmpty(a.JsSource))) - { - // if the action is using invalid javascript we need to do something about this - if (!Action.ValidateActionJs(a)) - { - // Make new Iaction - PlaceboAction pa = new PlaceboAction(a); - pa.JsFunctionName = "IActionProxy_" + pa.Alias.ToSafeAlias() + "()"; - allActions.Add(pa); - - } - else - { - allActions.Add(a); - } - } - } - catch (Exception ex) - { - Current.Logger.Error(ex, "Error initializing tree action"); - } - - } - - - return jSSerializer.Serialize(allActions); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs deleted file mode 100644 index ec59037775..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Script.Serialization; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.controls.Tree -{ - internal class JTreeContextMenuItem : JavaScriptConverter - { - - /// - /// Not implemented as we never need to Deserialize - /// - /// - /// - /// - /// - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) - { - throw new NotImplementedException(); - } - - public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) - { - //{ - // "id": "L", - // "label": "Create", - // "icon": "create.png", - // "visible": function(NODE, TREE_OBJ) { if (NODE.length != 1) return false; return TREE_OBJ.check("creatable", NODE); }, - // "action": function(NODE, TREE_OBJ) { TREE_OBJ.create(false, NODE); }, - //} - - - IAction a = (IAction)obj; - Dictionary data = new Dictionary(); - - data.Add("id", a.Letter); - data.Add("label", Current.Services.TextService.Localize(a.Alias)); - - if (a.Icon.StartsWith(".")) - { - StringBuilder sb = new StringBuilder(); - sb.Append(string.Format(""); - sb.Append(""); - data["label"] = sb.ToString(); - } - else - { - data.Add("icon", a.Icon); - } - - return data; - - } - - /// - /// TODO: Find out why we can't just return IAction as one type (JavaScriptSerializer doesn't seem to pick up on it) - /// - public override IEnumerable SupportedTypes - { - get - { - List types = new List(); - foreach (var a in Current.Actions) - { - types.Add(a.GetType()); - } - return types; - } - - - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs deleted file mode 100644 index 2d51a00e64..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/NodeInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace umbraco.controls.Tree -{ - /// - /// Simple data object to hold information about a node - /// - public class NodeInfo - { - /// - /// Gets or sets the id. - /// - /// The id. - public int Id { get; set; } - - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - - /// - /// Gets or sets the path as names. - /// - /// The path as names. - public string PathAsNames { get; set; } - - /// - /// Gets or sets the type of the node. - /// - /// The type of the node. - public string NodeType { get; set; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs deleted file mode 100644 index 7ec76bc6eb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/TreeControl.ascx.cs +++ /dev/null @@ -1,593 +0,0 @@ -using System; -using Umbraco.Core.Security; -using System.Collections.Generic; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.Models; -using Umbraco.Web.Trees; -using Umbraco.Web.UI.Controls; -using System.Text; -using umbraco.cms.presentation.Trees; -using System.Drawing; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.Security; - -namespace umbraco.controls.Tree -{ - - /// - /// The Umbraco tree control. - /// If this control doesn't exist on an UmbracoEnsuredPage it will not work. - /// - public partial class TreeControl : UmbracoUserControl, ITreeService - { - - /// - /// Set the defaults - /// - public TreeControl() - { - Width = Unit.Empty; - Height = Unit.Empty; - BackColor = Color.Empty; - CssClass = ""; - ManualInitialization = false; - } - - protected override void OnInit(EventArgs e) - { - base.OnInit(e); - EnableViewState = false; - } - - public enum TreeMode - { - Standard, Checkbox, InheritedCheckBox - } - - /// - /// If there is not application or tree specified in a query string then this is the application to load. - /// - private const string DEFAULT_APP = Constants.Applications.Content; - - private List m_ActiveTrees = new List(); - private List m_AllAppTrees = new List(); - private List m_ActiveTreeDefs = null; - private TreeMode m_TreeType = TreeMode.Standard; - private bool m_IsInit = false; - private TreeService m_TreeService = new TreeService(); - private string m_SelectedNodePath; - - #region Public Properties - - #region Style Properties - public string CssClass { get; set; } - public Unit Height { get; set; } - public Unit Width { get; set; } - public Color BackColor { get; set; } - #endregion - - #region TreeService parameters. - public string FunctionToCall - { - get { return m_TreeService.FunctionToCall; } - set - { - m_TreeService.FunctionToCall = value; - } - } - - public string NodeKey - { - get { return m_TreeService.NodeKey; } - set - { - m_TreeService.NodeKey = value; - } - } - - public int StartNodeID - { - get { return m_TreeService.StartNodeID; } - set - { - m_TreeService.StartNodeID = value; - } - } - - public string SelectedNodePath - { - get { return m_SelectedNodePath; } - set - { - m_SelectedNodePath = value; - } - } - - public string TreeType - { - get { return m_TreeService.TreeType; } - set - { - m_TreeService.TreeType = value; - } - } - - public bool ShowContextMenu - { - get { return m_TreeService.ShowContextMenu; } - set - { - m_TreeService.ShowContextMenu = value; - } - } - - public bool IsDialog - { - get { return m_TreeService.IsDialog; } - set - { - m_TreeService.IsDialog = value; - } - } - - public TreeDialogModes DialogMode - { - get { return m_TreeService.DialogMode; } - set - { - m_TreeService.DialogMode = value; - } - } - - - public string App - { - get - { - return GetCurrentApp(); - } - set - { - m_TreeService.App = value; - } - } - #endregion - - /// - /// Allows for checkboxes to be used with the tree. Default is standard. - /// - public TreeMode Mode - { - get - { - return m_TreeType; - } - set - { - m_TreeType = value; - } - } - - /// - /// Returns the required JavaScript as a string for the current application - /// - public string JSCurrApp - { - get - { - StringBuilder javascript = new StringBuilder(); - foreach (BaseTree bTree in m_AllAppTrees) - bTree.RenderJS(ref javascript); - return javascript.ToString(); - } - } - - /// - /// By default this is false. If set to true, then the code in the client side of the tree will force calling rebuildTree - /// to be called explicitly for the tree to render - /// - public bool ManualInitialization { get; set; } - - #endregion - - /// - /// Can be set explicitly which will override what is in query strings or what has been set by properties. - /// Useful for rendering out a tree dynamically with an instance of anoterh TreeService. - /// By using this method, it will undo any of the tree service public properties that may be set - /// on this object. - /// - public void SetTreeService(TreeService srv) - { - m_TreeService = srv; - Initialize(); - } - - /// - /// Initializes the control and looks up the tree structures that are required to be rendered. - /// Properties of the control (or SetTreeService) need to be set before pre render or calling - /// GetJSONContextMenu or GetJSONNode - /// - protected void Initialize() - { - //use the query strings if the TreeParams isn't explicitly set - if (m_TreeService == null) - { - m_TreeService = TreeRequestParams.FromQueryStrings().CreateTreeService(); - } - m_TreeService.App = GetCurrentApp(); - - // Validate permissions - if (ValidateCurrentUser() == false) - return; - - if (!Security.ValidateUserApp(GetCurrentApp())) - throw new ArgumentException("The current user doesn't have access to this application. Please contact the system administrator."); - - //find all tree definitions that have the current application alias that are ACTIVE. - //if an explicit tree has been requested, then only load that tree in. - //m_ActiveTreeDefs = TreeDefinitionCollection.Instance.FindActiveTrees(GetCurrentApp()); - - m_ActiveTreeDefs = Services.ApplicationTreeService.GetApplicationTrees(GetCurrentApp(), true).ToList(); - - if (!string.IsNullOrEmpty(this.TreeType)) - { - m_ActiveTreeDefs = m_ActiveTreeDefs - .Where(x => x.Alias == this.TreeType) - .ToList(); //this will only return 1 - } - - //find all tree defs that exists for the current application regardless of if they are active - var appTreeDefs = Services.ApplicationTreeService.GetApplicationTrees(GetCurrentApp()).ToList(); - - //Create the BaseTree's based on the tree definitions found - foreach (var treeDef in appTreeDefs) - { - //create the tree and initialize it - var bTree = LegacyTreeDataConverter.GetLegacyTreeForLegacyServices(treeDef); - //BaseTree bTree = treeDef.CreateInstance(); - bTree.SetTreeParameters(m_TreeService); - - //store the created tree - m_AllAppTrees.Add(bTree); - if (treeDef.Initialize) - m_ActiveTrees.Add(bTree); - } - - m_IsInit = true; - } - - /// - /// Validates the currently logged in user and ensures they are not timed out - /// - /// - private bool ValidateCurrentUser() - { - var identity = Context.GetCurrentIdentity( - //DO NOT AUTO-AUTH UNLESS THE CURRENT HANDLER IS WEBFORMS! - // Without this check, anything that is using this legacy API, like ui.Text will - // automatically log the back office user in even if it is a front-end request (if there is - // a back office user logged in. This can cause problems becaues the identity is changing mid - // request. For example: http://issues.umbraco.org/issue/U4-4010 - HttpContext.Current.CurrentHandler is Page); - - if (identity != null) - { - return true; - } - return false; - } - - - /// - /// This calls the databind method to bind the data binding syntax on the front-end. - /// - /// Databinding was used instead of inline tags in case the tree properties needed to be set - /// by other classes at runtime - /// - /// - /// - /// - /// This will initialize the control so all TreeService properties need to be set before hand - /// - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - if (!m_IsInit) - Initialize(); - - //Render out the JavaScript associated with all of the trees for the application - RenderTreeJS(); - - //apply the styles - if (Width != Unit.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.Width, Width.ToString()); - if (Height != Unit.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.Height, Height.ToString()); - if (BackColor != Color.Empty) - TreeContainer.Style.Add(HtmlTextWriterStyle.BackgroundColor, ColorTranslator.ToHtml(BackColor)); - if (CssClass != "") - { - TreeContainer.Attributes.Add("class", CssClass); - } - else - { - //add the default class - TreeContainer.Attributes.Add("class", "treeContainer"); - } - - - DataBind(); - } - - /// - /// Returns the JSON markup for the full context menu - /// - public string GetJSONContextMenu() - { - if (ShowContextMenu) - { - JTreeContextMenu menu = new JTreeContextMenu(); - return menu.RenderJSONMenu(); - } - else - { - return "{}"; - } - - } - - /// - /// Returns a string with javascript proxy methods for IActions that are using old javascript - /// - /// - public string GetLegacyIActionJavascript() - { - return LegacyTreeJavascript.GetLegacyIActionJavascript(); - } - - /// - /// Returns the JSON markup for one node - /// - /// - /// - /// - /// - /// This will initialize the control so all TreeService properties need to be set before hand - /// - public string GetJSONNode(string nodeId) - { - if (!m_IsInit) - Initialize(); - - if (string.IsNullOrEmpty(m_TreeService.TreeType)) - { - throw new ArgumentException("The TreeType is not set on the tree service"); - } - - BaseTree tree = m_ActiveTrees.Find( - delegate(BaseTree t) - { - return (t.TreeAlias == m_TreeService.TreeType); - } - ); - return tree.GetSerializedNodeData(nodeId); - } - - /// - /// Returns the JSON markup for the first node in the tree - /// - - public string GetJSONInitNode() - { - if (!m_IsInit) - Initialize(); - - //if there is only one tree to render, we don't want to have a node to hold sub trees, we just want the - //stand alone tree, so we'll just add a TreeType to the TreeService and ensure that the right method gets loaded in tree.aspx - if (m_ActiveTrees.Count == 1) - { - m_TreeService.TreeType = m_ActiveTreeDefs[0].Alias; - - //convert the menu to a string - //string initActions = (TreeSvc.ShowContextMenu ? Action.ToString(m_ActiveTrees[0].RootNodeActions) : ""); - - //Since there's only 1 tree, render out the tree's RootNode properties - XmlTree xTree = new XmlTree(); - xTree.Add(m_ActiveTrees[0].RootNode); - return xTree.ToString(); - } - else - { - - //If there is more than 1 tree for the application than render out a - //container node labelled with the current application. - XmlTree xTree = new XmlTree(); - XmlTreeNode xNode = XmlTreeNode.CreateRoot(new NullTree(GetCurrentApp())); - xNode.Text = Services.TextService.Localize("sections", GetCurrentApp()); - xNode.Source = m_TreeService.GetServiceUrl(); - xNode.Action = "javascript:" + global::Umbraco.Web.UI.Pages.ClientTools.Scripts.OpenDashboard(GetCurrentApp()); - xNode.NodeType = m_TreeService.App.ToLower(); - xNode.NodeID = "-1"; - xNode.Icon = ".sprTreeFolder"; - xTree.Add(xNode); - return xTree.ToString(); - } - } - - private void RenderTreeJS() - { - Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Trees_" + GetCurrentApp(), JSCurrApp, true); - } - - /// - /// Return the current application alias. If neither the TreeType of Application is specified - /// than return the default application. If the Application is null but there is a TreeType then - /// find the application that the tree type is associated with. - /// - private string GetCurrentApp() - { - //if theres an treetype specified but no application - if (string.IsNullOrEmpty(m_TreeService.App) && - !string.IsNullOrEmpty(m_TreeService.TreeType)) - { - TreeDefinition treeDef = TreeDefinitionCollection.Instance.FindTree(m_TreeService.TreeType); - if (treeDef != null) - return treeDef.App.Alias; - } - else if (!string.IsNullOrEmpty(m_TreeService.App)) - return m_TreeService.App; - - //if everything is null then return the default app - return DEFAULT_APP; - } - - /// - /// CssInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude2; - - /// - /// CssInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude3; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// JsInclude2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude2; - - /// - /// JsInclude3 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude3; - - /// - /// JsInclude4 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude4; - - /// - /// JsInclude5 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude5; - - /// - /// JsInclude6 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude6; - - /// - /// JsInclude8 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude8; - - /// - /// JsInclude11 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude11; - - /// - /// JsInclude7 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude7; - - /// - /// JsInclude12 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude12; - - /// - /// JsInclude9 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude9; - - /// - /// JsInclude10 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude10; - - /// - /// TreeContainer control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl TreeContainer; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs deleted file mode 100644 index f35f9d93b6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/create.aspx.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.Linq; -using System.Globalization; -using System.Web.UI; -using System.Xml; -using Umbraco.Core.IO; -using Umbraco.Web; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; -using Button = System.Web.UI.WebControls.Button; -using UserControl = System.Web.UI.UserControl; - -namespace umbraco.dialogs -{ - /// - /// Summary description for create. - /// - public partial class create : UmbracoEnsuredPage - { - protected Button ok; - - private string _app; - protected string App - { - get - { - if (_app == null) - { - _app = Request.CleanForXss("app"); - //validate the app - if (Services.SectionService.GetSections().Any(x => x.Alias.InvariantEquals(_app)) == false) - { - throw new InvalidOperationException("A requested app: " + Request.GetItemAsString("app") + " was not found"); - } - } - return _app; - } - } - - protected void Page_Load(object sender, EventArgs e) - { - // Put user code to initialize the page here - if (Request.GetItemAsString("nodeId") == "") - { - var appType = Services.TextService.Localize("sections", App).ToLower(); - pane_chooseNode.Text = Services.TextService.Localize("create/chooseNode", new[] { appType }) + "?"; - - DataBind(); - } - else - { - int nodeId = Request.GetItemAs("nodeId"); - //ensure they have access to create under this node!! - if (App.InvariantEquals(Constants.Applications.Media) || CheckCreatePermissions(nodeId)) - { - //var c = new CMSNode(nodeId); - var c = Services.EntityService.Get(nodeId); - path.Value = c.Path; - pane_chooseNode.Visible = false; - panel_buttons.Visible = false; - pane_chooseName.Visible = true; - var createDef = new XmlDocument(); - var defReader = new XmlTextReader(Server.MapPath(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/config/create/UI.xml")); - createDef.Load(defReader); - defReader.Close(); - - // Find definition for current nodeType - XmlNode def = createDef.SelectSingleNode("//nodeType [@alias = '" + App + "']"); - phCreate.Controls.Add(new UserControl().LoadControl(IOHelper.ResolveUrl(SystemDirectories.Umbraco) + def.SelectSingleNode("./usercontrol").FirstChild.Value)); - } - else - { - PageNameHolder.type = Umbraco.Web._Legacy.Controls.Feedback.feedbacktype.error; - PageNameHolder.Text = Services.TextService.Localize("rights") + " " + Services.TextService.Localize("error"); - JTree.DataBind(); - } - } - } - - protected override void OnPreRender(EventArgs e) - { - base.OnPreRender(e); - - ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference(IOHelper.ResolveUrl(SystemDirectories.WebServices) + "/legacyAjaxCalls.asmx")); - } - - private bool CheckCreatePermissions(int nodeId) - { - var c = Services.EntityService.Get(nodeId); - var permission = Services.UserService.GetPermissions(Security.CurrentUser, c.Path); - return permission.AssignedPermissions.Contains(ActionNew.Instance.Letter.ToString(CultureInfo.InvariantCulture), StringComparer.Ordinal); - } - - - /// - /// path control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlInputHidden path; - - /// - /// pane_chooseNode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_chooseNode; - - /// - /// JTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl JTree; - - /// - /// panel_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel panel_buttons; - - /// - /// PageNameHolder control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback PageNameHolder; - - /// - /// pane_chooseName control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_chooseName; - - /// - /// phCreate control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder phCreate; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm deleted file mode 100644 index 4df7696ed6..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/empty.htm +++ /dev/null @@ -1,9 +0,0 @@ - - - - Umbraco - empty document - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs deleted file mode 100644 index 23cc004e61..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/treePicker.aspx.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; -using umbraco.cms.presentation.Trees; - -namespace umbraco.dialogs -{ - [Obsolete("Use the TreeControl instead. This does however get used by the TreeService when requesting the tree init url.")] - public partial class treePicker : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - TreeParams = TreeRequestParams.FromQueryStrings().CreateTreeService(); - DataBind(); - - if(Request.QueryString["selected"] != null && TreeParams.TreeType == "content") - { - var currContent = Services.ContentService.GetById(int.Parse(Request.QueryString["selected"])); - if (currContent != null) - { - if (currContent.ParentId > 0) - DialogTree.SelectedNodePath = currContent.Path; - } - } - } - - protected TreeService TreeParams { get; private set; } - - /// - /// DialogTree control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.controls.Tree.TreeControl DialogTree; - - } -} From 10873c1dfc37271c2f0a5b82f7da5abf4b3b55a3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 17:41:02 +1100 Subject: [PATCH 145/278] Cleans up all old tree classes. woohoo! --- .../Composing/TypeLoaderTests.cs | 7 - .../common/mocks/umbraco.servervariables.js | 4 +- .../src/common/services/assets.service.js | 4 - src/Umbraco.Web.UI/Umbraco/Create.aspx.cs | 6 - .../developer/Macros/EditMacro.aspx.cs | 4 - .../Editors/BackOfficeController.cs | 34 +- .../Editors/BackOfficeServerVariables.cs | 1 - .../Editors/PackageInstallController.cs | 5 - src/Umbraco.Web/Models/Trees/MenuItem.cs | 27 +- .../Trees/ApplicationTreeController.cs | 23 - .../Trees/ApplicationTreeExtensions.cs | 121 ---- src/Umbraco.Web/Trees/LegacyTreeController.cs | 109 ---- .../Trees/LegacyTreeDataConverter.cs | 245 -------- src/Umbraco.Web/Trees/LegacyTreeJavascript.cs | 88 --- src/Umbraco.Web/Trees/LegacyTreeParams.cs | 37 -- src/Umbraco.Web/Trees/UsersTreeController.cs | 96 --- src/Umbraco.Web/TypeLoaderExtensions.cs | 13 +- src/Umbraco.Web/Umbraco.Web.csproj | 23 +- .../umbraco/Trees/BaseTree.cs | 571 ------------------ .../umbraco/Trees/ITreeService.cs | 20 - .../umbraco/Trees/NodeActionsEventArgs.cs | 18 - .../umbraco/Trees/NullTree.cs | 62 -- .../umbraco/Trees/TreeDefinition.cs | 110 ---- .../umbraco/Trees/TreeDefinitionCollection.cs | 187 ------ .../umbraco/Trees/TreeDialogModes.cs | 17 - .../umbraco/Trees/TreeEventArgs.cs | 26 - .../umbraco/Trees/TreeRequestParams.cs | 135 ----- .../umbraco/Trees/TreeService.cs | 132 ---- .../umbraco/Trees/TreeUrlGenerator.cs | 189 ------ .../umbraco/Trees/XmlTree.cs | 455 -------------- .../developer/Packages/installer.aspx.cs | 103 ---- 31 files changed, 5 insertions(+), 2867 deletions(-) delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeController.cs delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeJavascript.cs delete mode 100644 src/Umbraco.Web/Trees/LegacyTreeParams.cs delete mode 100644 src/Umbraco.Web/Trees/UsersTreeController.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index d7f2e7dd53..9b23ec3d6b 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -275,13 +275,6 @@ AnotherContentFinder Assert.AreEqual(34, actions.Count()); } - [Test] - public void Resolves_Trees() - { - var trees = _typeLoader.GetTrees(); - Assert.AreEqual(1, trees.Count()); - } - [Test] public void GetDataEditors() { diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 7ba14485d4..da6f78a6a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -13,8 +13,6 @@ Umbraco.Sys.ServerVariables = { "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/", "macroApiBaseUrl": "/umbraco/Api/Macro/", "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/", - //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff - "legacyTreeJs": "/belle/lib/lazyload/empty.js", "serverVarsJs": "/belle/lib/lazyload/empty.js", "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/", "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/", @@ -39,4 +37,4 @@ Umbraco.Sys.ServerVariables = { assemblyVersion: "1", version: "7" } -}; \ No newline at end of file +}; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index a331af899b..e7f40f4814 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -141,10 +141,6 @@ angular.module('umbraco.services') var self = this; return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - return loadMomentLocaleForCurrentUser(); }); } diff --git a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs index 220b86fa5f..7848a5976b 100644 --- a/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/Create.aspx.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Web; -using System.Xml; -using Umbraco.Core; -using Umbraco.Core.IO; -using umbraco.cms.presentation.Trees; using Umbraco.Web._Legacy.UI; namespace Umbraco.Web.UI.Umbraco diff --git a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs index b9c0577c95..96433be9cc 100644 --- a/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs +++ b/src/Umbraco.Web.UI/Umbraco/developer/Macros/EditMacro.aspx.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Web.UI; using System.Web.UI.WebControls; @@ -9,12 +8,9 @@ using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using umbraco.cms.presentation.Trees; using System.Linq; -using Umbraco.Web.UI; using Umbraco.Web.UI.Pages; using Umbraco.Core.Services; -using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web._Legacy.Controls; diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index f3bda66b51..15554b7f50 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -520,38 +520,6 @@ namespace Umbraco.Web.Editors return true; } - /// - /// Returns the JavaScript blocks for any legacy trees declared - /// - /// - [UmbracoAuthorize(Order = 0)] - [MinifyJavaScriptResult(Order = 1)] - public JavaScriptResult LegacyTreeJs() - { - Func getResult = () => - { - var javascript = new StringBuilder(); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript()); - javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript()); - //add all of the menu blocks - foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock)) - { - javascript.AppendLine(file); - } - return javascript.ToString(); - }; - - //cache the result if debugging is disabled - var result = HttpContext.IsDebuggingEnabled - ? getResult() - : ApplicationCache.RuntimeCache.GetCacheItem( - typeof(BackOfficeController) + "LegacyTreeJs", - () => getResult(), - new TimeSpan(0, 10, 0)); - - return JavaScript(result); - } - internal static IEnumerable GetLegacyActionJsForActions(LegacyJsActionType type, IEnumerable values) { var blockList = new List(); @@ -582,7 +550,7 @@ namespace Umbraco.Web.Editors } /// - /// Renders out all JavaScript references that have bee declared in IActions + /// Renders out all JavaScript references that have been declared in IActions /// private static IEnumerable GetLegacyActionJs(LegacyJsActionType type) { diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 8eb1c4247f..b0cdcb72b9 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -112,7 +112,6 @@ namespace Umbraco.Web.Editors {"externalLoginsUrl", _urlHelper.Action("ExternalLogin", "BackOffice")}, {"externalLinkLoginsUrl", _urlHelper.Action("LinkLogin", "BackOffice")}, - {"legacyTreeJs", _urlHelper.Action("LegacyTreeJs", "BackOffice")}, {"manifestAssetList", _urlHelper.Action("GetManifestAssetList", "BackOffice")}, {"gridConfig", _urlHelper.Action("GetGridConfig", "BackOffice")}, //TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6111a931e3..94465feab8 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using System.Web.Http; using System.Xml; using umbraco.cms.businesslogic.packager; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -249,7 +248,6 @@ namespace Umbraco.Web.Editors // trigger the UninstalledPackage event PackagingService.OnUninstalledPackage(new UninstallPackageEventArgs(summary, false)); - TreeDefinitionCollection.Instance.ReRegisterTrees(); } /// @@ -587,9 +585,6 @@ namespace Umbraco.Web.Editors var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. - //these bits are super old, but cant find another way to do this currently - global::umbraco.cms.presentation.Trees.TreeDefinitionCollection.Instance.ReRegisterTrees(); var redirectUrl = ""; if (ins.Control.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index a854bbe777..88d772b939 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -201,32 +201,7 @@ namespace Umbraco.Web.Models.Trees } } } - - internal void ConvertLegacyFileSystemMenuItem(string path, string nodeType, string currentSection) - { - // try to get a URL/title from the legacy action, - // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. - var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, - path, - nodeType, - path, currentSection); - if (attempt) - { - var action = attempt.Result; - LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + path + "' ?"); - } - } - } + #endregion } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 53c8d26bc2..b4d217fe62 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -167,12 +167,6 @@ namespace Umbraco.Web.Trees return null; } - var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - return legacyAttempt.Result; - } - throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); } @@ -216,23 +210,6 @@ namespace Umbraco.Web.Trees return sectionRoot; } - var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias); - if (legacyAttempt.Success) - { - var sectionRoot = TreeRootNode.CreateSingleTreeRoot( - rootId, - "", //TODO: I think we'll need this in this situation! - Url.GetUmbracoApiService("GetMenu", rootId) - + "&parentId=" + rootId - + "&treeType=" + application - + "§ion=" + application, - "", //TODO: I think we'll need this in this situation! - legacyAttempt.Result); - - - sectionRoot.AdditionalData.Add("treeAlias", configTree.Alias); - return sectionRoot; - } throw new ApplicationException("Could not render a tree for type " + configTree.Alias); } diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index af9eb094d0..171601a338 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -13,12 +13,10 @@ using Umbraco.Core; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Core.Services; using Current = Umbraco.Web.Composing.Current; using ApplicationTree = Umbraco.Core.Models.ApplicationTree; -using UrlHelper = System.Web.Http.Routing.UrlHelper; namespace Umbraco.Web.Trees { @@ -191,125 +189,6 @@ namespace Umbraco.Web.Trees return Attempt.Succeed(instance.GetNodes(id, formCollection)); } - internal static Attempt TryGetRootNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xmlTreeNodeAttempt = TryGetRootXmlNodeFromLegacyTree(appTree, formCollection, urlHelper); - if (xmlTreeNodeAttempt.Success == false) - { - return Attempt.Fail(xmlTreeNodeAttempt.Exception); - } - - //the root can potentially be null, in that case we'll just return a null success which means it won't be included - if (xmlTreeNodeAttempt.Result == null) - { - return Attempt.Succeed(null); - } - - //var temp = new LegacyTreeController(xmlTreeNodeAttempt.Result, appTree.Alias, currentSection, urlHelper); - var temp = new TreeControllerBaseStuffForLegacy(appTree.Alias, xmlTreeNodeAttempt.Result.Text, urlHelper); - var newRoot = temp.GetRootNode(formCollection); - - return Attempt.Succeed(newRoot); - - } - - internal static Attempt TryGetRootXmlNodeFromLegacyTree(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - bTree.SetTreeParameters(treeParams); - - var xmlRoot = bTree.RootNode; - - return Attempt.Succeed(xmlRoot); - } - - internal static Attempt TryGetLegacyTreeDef(this ApplicationTree appTree) - { - //This is how the legacy trees worked.... - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - return treeDef == null - ? Attempt.Fail(new InstanceNotFoundException("Could not find tree of type " + appTree.Alias)) - : Attempt.Succeed(treeDef); - } - - internal static Attempt TryLoadFromLegacyTree(this ApplicationTree appTree, string id, FormDataCollection formCollection, UrlHelper urlHelper, string currentSection) - { - var xTreeAttempt = appTree.TryGetXmlTree(id, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - return Attempt.Succeed(LegacyTreeDataConverter.ConvertFromLegacy(id, xTreeAttempt.Result, urlHelper, currentSection, formCollection)); - } - - internal static Attempt TryGetMenuFromLegacyTreeRootNode(this ApplicationTree appTree, FormDataCollection formCollection, UrlHelper urlHelper) - { - var rootAttempt = appTree.TryGetRootXmlNodeFromLegacyTree(formCollection, urlHelper); - if (rootAttempt.Success == false) - { - return Attempt.Fail(rootAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(rootAttempt.Result, currentSection); - return Attempt.Succeed(result); - } - - internal static Attempt TryGetMenuFromLegacyTreeNode(this ApplicationTree appTree, string parentId, string nodeId, FormDataCollection formCollection, UrlHelper urlHelper) - { - var xTreeAttempt = appTree.TryGetXmlTree(parentId, formCollection); - if (xTreeAttempt.Success == false) - { - return Attempt.Fail(xTreeAttempt.Exception); - } - - var currentSection = formCollection.GetRequiredString("section"); - - var result = LegacyTreeDataConverter.ConvertFromLegacyMenu(nodeId, xTreeAttempt.Result, currentSection); - if (result == null) - { - return Attempt.Fail(new ApplicationException("Could not find the node with id " + nodeId + " in the collection of nodes contained with parent id " + parentId)); - } - return Attempt.Succeed(result); - } - - private static Attempt TryGetXmlTree(this ApplicationTree appTree, string id, FormDataCollection formCollection) - { - var treeDefAttempt = appTree.TryGetLegacyTreeDef(); - if (treeDefAttempt.Success == false) - { - return Attempt.Fail(treeDefAttempt.Exception); - } - var treeDef = treeDefAttempt.Result; - //This is how the legacy trees worked.... - var bTree = treeDef.CreateInstance(); - var treeParams = new LegacyTreeParams(formCollection); - - //we currently only support an integer id or a string id, we'll refactor how this works - //later but we'll get this working first - int startId; - if (int.TryParse(id, out startId)) - { - treeParams.StartNodeID = startId; - } - else - { - treeParams.NodeKey = id; - } - var xTree = new XmlTree(); - bTree.SetTreeParameters(treeParams); - bTree.Render(ref xTree); - return Attempt.Succeed(xTree); - } - } } diff --git a/src/Umbraco.Web/Trees/LegacyTreeController.cs b/src/Umbraco.Web/Trees/LegacyTreeController.cs deleted file mode 100644 index 87035e0632..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeController.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - /// - /// This is used to output JSON from legacy trees - /// - [PluginController("UmbracoTrees"), LegacyTreeAuthorizeAttribute] - public class LegacyTreeController : TreeControllerBase - { - private readonly XmlTreeNode _xmlTreeNode; - private readonly string _currentSection; - - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - return LegacyTreeDataConverter.ConvertFromLegacy( - _xmlTreeNode.NodeID, - _xmlTreeNode, - Url, - _currentSection, - queryStrings, - isRoot: true); - } - - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var tree = GetTree(queryStrings); - var attempt = tree.TryLoadFromLegacyTree(id, queryStrings, Url, tree.ApplicationAlias); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render tree {TreeType} for node id {NodeId}", queryStrings.GetRequiredString("treeType"), id); - throw new ApplicationException("Could not render tree " + queryStrings.GetRequiredString("treeType") + " for node id " + id); - } - - return attempt.Result; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - //get the parent id from the query strings - var parentId = queryStrings.GetRequiredString("parentId"); - var tree = GetTree(queryStrings); - - var rootIds = new[] - { - Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinContent.ToString(CultureInfo.InvariantCulture), - Core.Constants.System.RecycleBinMedia.ToString(CultureInfo.InvariantCulture) - }; - - //if the id and the parentId are both -1 then we need to get the menu for the root node - if (rootIds.Contains(id) && parentId == "-1") - { - var attempt = tree.TryGetMenuFromLegacyTreeRootNode(queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for root node for treeType {TreeType}", queryStrings.GetRequiredString("treeType")); - throw new ApplicationException("Could not render menu for root node for treeType " + queryStrings.GetRequiredString("treeType")); - } - - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - else - { - var attempt = tree.TryGetMenuFromLegacyTreeNode(parentId, id, queryStrings, Url); - if (attempt.Success == false) - { - Logger.Error(attempt.Exception, "Could not render menu for treeType {TreeType} for node id {ParentNodeId}", queryStrings.GetRequiredString("treeType"), parentId); - throw new ApplicationException("Could not render menu for treeType " + queryStrings.GetRequiredString("treeType") + " for node id " + parentId); - } - foreach (var menuItem in attempt.Result.Items) - { - menuItem.Name = Services.TextService.Localize("actions", menuItem.Alias); - } - return attempt.Result; - } - } - - public override string RootNodeDisplayName { get; } - - public override string TreeAlias { get; } - - private ApplicationTree GetTree(FormDataCollection queryStrings) - { - //need to ensure we have a tree type - var treeType = queryStrings.GetRequiredString("treeType"); - //now we'll look up that tree - var tree = Services.ApplicationTreeService.GetByAlias(treeType); - if (tree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - return tree; - } - - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index a0259ab247..131c7954c8 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Routing; -using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Web._Legacy.Actions; using Umbraco.Web.Composing; @@ -19,154 +17,6 @@ namespace Umbraco.Web.Trees /// internal class LegacyTreeDataConverter { - internal static BaseTree GetLegacyTreeForLegacyServices(Core.Models.ApplicationTree appTree) - { - if (appTree == null) throw new ArgumentNullException("appTree"); - - BaseTree tree; - - var controllerAttempt = appTree.TryGetControllerTree(); - if (controllerAttempt.Success) - { - Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services."); - return null; - - //var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); - //if (legacyAtt == null) - //{ - // Current.Logger.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); - // return null; - //} - - //var treeDef = new TreeDefinition( - // legacyAtt.BaseTreeType, - // new ApplicationTree(true, appTree.SortOrder, appTree.ApplicationAlias, appTree.Alias, appTree.Title, appTree.IconClosed, appTree.IconOpened, legacyAtt.BaseTreeType.GetFullNameWithAssembly()), - // new Section(appTree.Alias, appTree.Alias, "", 0)); - - //tree = treeDef.CreateInstance(); - //tree.TreeAlias = appTree.Alias; - - } - else - { - //get the tree that we need to render - var treeDef = TreeDefinitionCollection.Instance.FindTree(appTree.Alias); - if (treeDef == null) - { - return null; - } - tree = treeDef.CreateInstance(); - } - - return tree; - } - - /// - /// This is used by any legacy services that require rendering a BaseTree, if a new controller tree is detected it will try to invoke it's legacy predecessor. - /// - /// - /// - /// - internal static BaseTree GetLegacyTreeForLegacyServices(IApplicationTreeService appTreeService, string treeType) - { - if (appTreeService == null) throw new ArgumentNullException("appTreeService"); - if (treeType == null) throw new ArgumentNullException("treeType"); - - //first get the app tree definition so we can then figure out if we need to load by legacy or new - //now we'll look up that tree - var appTree = appTreeService.GetByAlias(treeType); - if (appTree == null) - throw new InvalidOperationException("No tree found with alias " + treeType); - - return GetLegacyTreeForLegacyServices(appTree); - } - - /// - /// Gets the menu item collection from a legacy tree node based on it's parent node's child collection - /// - /// The node id - /// The node collection that contains the node id - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(string nodeId, XmlTree xmlTree, string currentSection) - { - var xmlTreeNode = xmlTree.treeCollection.FirstOrDefault(x => x.NodeID == nodeId); - if (xmlTreeNode == null) - { - return null; - } - - return ConvertFromLegacyMenu(xmlTreeNode, currentSection); - } - - /// - /// Gets the menu item collection from a legacy tree node - /// - /// - /// - /// - internal static MenuItemCollection ConvertFromLegacyMenu(XmlTreeNode xmlTreeNode, string currentSection) - { - var collection = new MenuItemCollection(); - - var menuItems = xmlTreeNode.Menu.ToArray(); - var numAdded = 0; - var seperators = new List(); - foreach (var t in menuItems) - { - if (t is ContextMenuSeperator && numAdded > 0) - { - //store the index for which the seperator should be placed - seperators.Add(collection.Items.Count()); - } - else - { - var menuItem = collection.Items.Add(t, Current.Services.TextService.Localize("actions", t.Alias)); - - var currentAction = t; - - // try to get a URL/title from the legacy action - var attempt = GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection); - if (attempt) - { - var action = attempt.Result; - menuItem.LaunchDialogUrl(action.Url, action.DialogTitle); - } - else - { - // if that doesn't work, try to get the legacy confirm view - var attempt2 = GetLegacyConfirmView(currentAction); - if (attempt2) - { - var view = attempt2.Result; - var textService = Current.Services.TextService; - menuItem.LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + xmlTreeNode.Text + "' ?"); - } - else - { - // if that doesn't work and there's no jsAction in there already then add the legacy js method call - if (menuItem.AdditionalData.ContainsKey(MenuItem.JsActionKey) == false) - menuItem.ExecuteLegacyJs(menuItem.Action.JsFunctionName); - } - } - - numAdded++; - } - } - var length = collection.Items.Count(); - foreach (var s in seperators) - { - if (length >= s) - { - collection.Items.ElementAt(s).SeperatorBefore = true; - } - } - - return collection; - } - - - /// /// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible /// @@ -261,101 +111,6 @@ namespace Umbraco.Web.Trees return Attempt.Fail(); } - /// - /// Converts a legacy XmlTreeNode to a new TreeNode - /// - /// - /// - /// - /// - /// - /// The current query strings for the request - this is used to append the query strings to the menu URL of the item being rendered since the menu - /// actually belongs to this same node (request) the query strings need to exist so the menu can be rendered in some cases. - /// - /// - /// - internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings, bool isRoot = false) - { - // /umbraco/tree.aspx?rnd=d0d0ff11a1c347dabfaa0fc75effcc2a&id=1046&treeType=content&contextMenu=false&isDialog=false - - //we need to convert the node source to our legacy tree controller - var childNodesSource = urlHelper.GetUmbracoApiService("GetNodes"); - - var childQuery = (xmlTreeNode.Source.IsNullOrWhiteSpace() || xmlTreeNode.Source.IndexOf('?') == -1) - ? "" - : xmlTreeNode.Source.Substring(xmlTreeNode.Source.IndexOf('?')); - - //append the query strings - childNodesSource = childNodesSource.AppendQueryStringToUrl(childQuery); - - //for the menu source we need to detect if this is a root node since we'll need to set the parentId and id to -1 - // for which we'll handle correctly on the server side. - //if there are no menu items, then this will be empty - var menuSource = ""; - if (xmlTreeNode.Menu != null && xmlTreeNode.Menu.Any()) - { - menuSource = urlHelper.GetUmbracoApiService("GetMenu"); - //these are the absolute required query strings - var menuQueryStrings = new Dictionary - { - {"id", (isRoot ? "-1" : xmlTreeNode.NodeID)}, - {"treeType", xmlTreeNode.TreeType}, - {"parentId", (isRoot ? "-1" : parentId)}, - {"section", currentSection} - }; - //append the extra ones on this request - foreach (var i in currentQueryStrings.Where(x => menuQueryStrings.Keys.Contains(x.Key) == false)) - { - menuQueryStrings.Add(i.Key, i.Value); - } - - menuSource = menuSource.AppendQueryStringToUrl(menuQueryStrings.ToQueryString()); - } - - - //TODO: Might need to add stuff to additional attributes - - var node = new TreeNode(xmlTreeNode.NodeID, isRoot ? null : parentId, childNodesSource, menuSource) - { - HasChildren = xmlTreeNode.HasChildren, - Icon = xmlTreeNode.Icon, - Name = xmlTreeNode.Text, - NodeType = xmlTreeNode.NodeType - }; - if (isRoot) - { - node.AdditionalData.Add("treeAlias", xmlTreeNode.TreeType); - } - - foreach (var appliedClass in xmlTreeNode.Style.AppliedClasses) - { - node.CssClasses.Add(appliedClass); - } - - //This is a special case scenario, we know that content/media works based on the normal Belle routing/editing so we'll ensure we don't - // pass in the legacy JS handler so we do it the new way, for all other trees (Currently, this is a WIP), we'll render - // the legacy js callback,. - var knownNonLegacyNodeTypes = new[] { "content", "contentRecycleBin", "mediaRecyleBin", "media" }; - if (knownNonLegacyNodeTypes.InvariantContains(xmlTreeNode.NodeType) == false) - { - node.AssignLegacyJsCallback(xmlTreeNode.Action); - } - return node; - } - - internal static TreeNodeCollection ConvertFromLegacy(string parentId, XmlTree xmlTree, UrlHelper urlHelper, string currentSection, FormDataCollection currentQueryStrings) - { - //TODO: Once we get the editor URL stuff working we'll need to figure out how to convert - // that over to use the old school ui.xml stuff for these old trees and however the old menu items worked. - - var collection = new TreeNodeCollection(); - foreach (var x in xmlTree.treeCollection) - { - collection.Add(ConvertFromLegacy(parentId, x, urlHelper, currentSection, currentQueryStrings)); - } - return collection; - } - internal class LegacyUrlAction { public LegacyUrlAction(string url, string dialogTitle) diff --git a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs b/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs deleted file mode 100644 index 0ae9916d4b..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeJavascript.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Logging; -using umbraco.cms.presentation.Trees; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Web.Trees -{ - /// - /// A class used to render the legacy JS requirements for trees and IActions. - /// - internal static class LegacyTreeJavascript - { - /// - /// If any legacy tree requires any JS rendering then we will compile a JS output of the combination. - /// - /// - public static string GetLegacyTreeJavascript() - { - //find all tree defs that exists for the current application regardless of if they are active - List appTreeDefs = TreeDefinitionCollection.Instance; - //Create the BaseTree's based on the tree definitions found - var legacyTrees = appTreeDefs.Select(treeDef => treeDef.CreateInstance()).ToList(); - var javascript = new StringBuilder(); - foreach (var bTree in legacyTrees) - { - try - { - bTree.RenderJS(ref javascript); - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Could not load the JS from the legacy tree {TreeAlias}", bTree.TreeAlias); - } - } - - return ReplaceLegacyJs(javascript.ToString()); - } - - /// - /// Returns a string with javascript proxy methods for IActions that are using old javascript - /// - /// - public static string GetLegacyIActionJavascript() - { - var js = new StringBuilder(); - foreach (var a in Current.Actions) - { - // NH: Added a try/catch block to this as an error in a 3rd party action can crash the whole menu initialization - try - { - if (string.IsNullOrEmpty(a.Alias) == false && (string.IsNullOrEmpty(a.JsFunctionName) == false || string.IsNullOrEmpty(a.JsSource) == false)) - { - // if the action is using invalid javascript we need to do something about this - if (global::Umbraco.Web._Legacy.Actions.Action.ValidateActionJs(a) == false) - { - js.AppendLine("function IActionProxy_" + a.Alias.ToSafeAlias() + "() {"); - js.AppendLine(global::Umbraco.Web._Legacy.Actions.Action.ConvertLegacyJs(a.JsFunctionName)); - js.AppendLine("}"); - } - } - } - catch (Exception ex) - { - Current.Logger.Error(typeof(LegacyTreeJavascript), ex, "Error initializing tree action"); - } - } - - if (js.Length != 0) - { - js.Insert(0, "// This javascript is autogenerated by Umbraco to ensure legacy compatiblity with old context menu items\n\n"); - } - - return ReplaceLegacyJs(js.ToString()); - } - - private static string ReplaceLegacyJs(string js){ - js = js.Replace("parent.right.document.location", "UmbClientMgr.getFakeFrame()"); - js = js.Replace("right.document.location", "UmbClientMgr.getFakeFrame()"); - - return js; - } - } -} diff --git a/src/Umbraco.Web/Trees/LegacyTreeParams.cs b/src/Umbraco.Web/Trees/LegacyTreeParams.cs deleted file mode 100644 index 13b84edd38..0000000000 --- a/src/Umbraco.Web/Trees/LegacyTreeParams.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using umbraco.cms.presentation.Trees; - -namespace Umbraco.Web.Trees -{ - - //Temporary, but necessary until we refactor trees in general - internal class LegacyTreeParams : ITreeService - { - public LegacyTreeParams() - { - - } - - public LegacyTreeParams(IEnumerable> formCollection) - { - if (formCollection != null) - { - var p = TreeRequestParams.FromDictionary(formCollection.ToDictionary(x => x.Key, x => x.Value)); - NodeKey = p.NodeKey; - StartNodeID = p.StartNodeID; - ShowContextMenu = p.ShowContextMenu; - IsDialog = p.IsDialog; - DialogMode = p.DialogMode; - FunctionToCall = p.FunctionToCall; - } - } - - public string NodeKey { get; set; } - public int StartNodeID { get; set; } - public bool ShowContextMenu { get; set; } - public bool IsDialog { get; set; } - public TreeDialogModes DialogMode { get; set; } - public string FunctionToCall { get; set; } - } -} diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs deleted file mode 100644 index 2fe08e25fb..0000000000 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Web._Legacy.Actions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Users)] - [Tree(Constants.Applications.Users, Constants.Trees.Users, null)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class UsersTreeController : TreeController - { - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - - var users = new List(Services.UserService.GetAll(0, int.MaxValue, out _)); - var currentUser = UmbracoContext.Current.Security.CurrentUser; - var hideDisabledUsers = UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; - - foreach (var user in users.OrderBy(x => x.IsApproved == false)) - { - // hide disabled user - if (user.IsApproved == false && hideDisabledUsers) - continue; - - if (user.IsSuper()) - { - // only super can see super - if (!currentUser.IsSuper()) continue; - } - else if (user.IsAdmin()) - { - // only admins can see admins - if (!currentUser.IsAdmin()) continue; - } - - var node = CreateTreeNode( - user.Id.ToString(CultureInfo.InvariantCulture), - "-1", - queryStrings, - user.Name, - "icon-user", - false, - "/" + queryStrings.GetValue("application") + "/framed/" - + Uri.EscapeDataString("users/EditUser.aspx?id=" + user.Id)); - - if (user.IsApproved == false) - node.CssClasses.Add("not-published"); - - nodes.Add(node); - } - - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - - if (id == Constants.System.Root.ToInvariantString()) - { - // Root actions - menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) - .ConvertLegacyMenuItem(null, "users", queryStrings.GetValue("application")); - - menu.Items.Add( - Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); - return menu; - } - - // If administator, don't create a menu - if (id == "0") - return menu; - - menu.Items.Add(new DisableUser() - { - Name = Services.TextService.Localize("actions", "disable") - }); - - return menu; - } - } -} diff --git a/src/Umbraco.Web/TypeLoaderExtensions.cs b/src/Umbraco.Web/TypeLoaderExtensions.cs index a1209abccf..710c342115 100644 --- a/src/Umbraco.Web/TypeLoaderExtensions.cs +++ b/src/Umbraco.Web/TypeLoaderExtensions.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Media; using Umbraco.Web.Mvc; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using umbraco.cms.presentation.Trees; using Umbraco.Core.Composing; using Umbraco.Web._Legacy.Actions; @@ -43,17 +42,7 @@ namespace Umbraco.Web { return mgr.GetTypes(); } - - /// - /// Returns all available ITrees in application - /// - /// - /// - internal static IEnumerable GetTrees(this TypeLoader mgr) - { - return mgr.GetTypes(); - } - + /// /// Returns all available ISearchableTrees in application /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 37f4868bc1..faf08849ce 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -177,6 +177,7 @@ + @@ -510,17 +511,12 @@ - - - ASPXCodeBehind - ASPXCodeBehind - @@ -637,7 +633,6 @@ - @@ -961,11 +956,7 @@ - - - - @@ -1326,23 +1317,11 @@ rollBack.aspx - - Code - - - - - - - - - - CheckForUpgrade.asmx diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs deleted file mode 100644 index 1592ff68bb..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs +++ /dev/null @@ -1,571 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Web.Composing; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// All ITree's should inherit from BaseTree. - /// - public abstract class BaseTree : ITreeService //, IApplicationEventHandler - { - protected BaseTree(string application) - { - app = application; - } - - protected const string FolderIcon = "icon-folder"; - protected const string FolderIconOpen = "icon-folder"; - - internal static string GetTreePathFromFilePath(string filePath) - => GetTreePathFromFilePath(filePath, true, false); - - internal static string GetTreePathFromFilePath(string filePath, bool includeInit, bool urlEncode) - { - List treePath = new List(); - treePath.Add("-1"); - if (includeInit) treePath.Add("init"); - string[] pathPaths = filePath.Split('/'); - pathPaths.Reverse(); - for (int p = 0; p < pathPaths.Length; p++) - { - var s = string.Join("/", pathPaths.Take(p + 1).ToArray()); - if (urlEncode) s = WebUtility.UrlEncode(s); - treePath.Add(s); - } - string sPath = string.Join(",", treePath.ToArray()); - return sPath; - } - - /// - /// Returns the node definition of the root node for this tree - /// - public XmlTreeNode RootNode - { - get - { - Initialize(); - return m_initNode; - } - } - - /// - /// By default the init actions that are allowed for all trees are Create, Reload Nodes. - /// These are the menu items that show up in the context menu for the root node of the current tree. - /// Should be used in conjunction with the RootNode property - /// - public List RootNodeActions - { - get - { - Initialize(); - return m_initActions; - } - } - - /// - /// The actions that are allowed to be performed on this tree. These are the items that may show up on the - /// context menu for a given node. - /// - public List AllowedActions - { - get - { - Initialize(); - return m_allowedActions; - } - } - - /// - /// The tree alias name. By default, if a BaseTree is instantiated by it's TreeDefinition, then the TreeAlias will be - /// the name defined in the database. Inheritors can override this property to set the TreeAlias to whatever they choose. - /// - public virtual string TreeAlias - { - get - { - if (string.IsNullOrEmpty(m_treeAlias)) - { - TreeDefinition treeDef = TreeDefinitionCollection.Instance.FindTree(this); - m_treeAlias = (treeDef != null ? treeDef.Tree.Alias : ""); - } - - return m_treeAlias; - } - internal set { m_treeAlias = value; } - } - private string m_treeAlias; - - #region ITreeService Members - - /// - /// By default the start node id will be -1 which will return all of the nodes - /// - public virtual int StartNodeID - { - get { return -1; } - } - - public bool ShowContextMenu - { - get { return m_showContextMenu; } - set { m_showContextMenu = value; } - } - - public bool IsDialog - { - get { return m_isDialog; } - set { m_isDialog = value; } - } - - /// - /// The NodeKey is a string representation of the nodeID. Generally this is used for tree's whos node's unique key value is a string in instead - /// of an integer such as folder names. - /// - public string NodeKey - { - get { return m_nodeKey; } - set { m_nodeKey = value; } - } - - public string FunctionToCall - { - get { return m_functionToCall; } - set { m_functionToCall = value; } - } - - public TreeDialogModes DialogMode - { - get { return m_dialogMode; } - set { m_dialogMode = value; } - } - - #endregion - - #region ITree Members - - /// - /// The ID of the node to render. This is generally set before calling the render method of the tree. If it is not set then the - /// StartNodeID property is used as the node ID to render. - /// - public virtual int id - { - set { m_id = value; } - get { return m_id; } - } - public virtual string app - { - set { m_app = value; } - get { return m_app; } - } - - /// - /// Renders out any JavaScript methods that may be required for tree functionality. Generally used to load the editor page when - /// a user clicks on a tree node. - /// - /// - public abstract void RenderJS(ref StringBuilder Javascript); - - /// - /// Classes need to override thid method to create the nodes for the XmlTree - /// - /// - public abstract void Render(ref XmlTree tree); - - #endregion - - protected int m_id; - protected string m_app; - protected XmlTreeNode m_initNode; - private List m_initActions = new List(); - private List m_allowedActions = new List(); - - //these are the request parameters that can be specified. - //since we want to remove the querystring/httpcontext dependency from - //our trees, we need to define these as properties. - private bool m_showContextMenu = true; - private bool m_isDialog = false; - private TreeDialogModes m_dialogMode = TreeDialogModes.none; - private string m_nodeKey = ""; - private string m_functionToCall = ""; - - private bool m_isInitialized = false; - - private XmlTree m_xTree = new XmlTree(); - - /// - /// Provides easy access to the ServiceContext - /// - protected internal ServiceContext Services - { - get { return Current.Services; } - } - - /// - /// Initializes the class if it hasn't been done already - /// - protected void Initialize() - { - if (!m_isInitialized) - { - //VERY IMPORTANT! otherwise it will go infinite loop! - m_isInitialized = true; - - CreateAllowedActions(); //first create the allowed actions - - //raise the event, allow developers to modify the collection - var nodeActions = new NodeActionsEventArgs(false, m_allowedActions); - OnNodeActionsCreated(nodeActions); - m_allowedActions = nodeActions.AllowedActions; - - CreateRootNodeActions();//then create the root node actions - - var rootActions = new NodeActionsEventArgs(true, m_initActions); - OnNodeActionsCreated(rootActions); - m_initActions = rootActions.AllowedActions; - - CreateRootNode(); //finally, create the root node itself - } - } - - /// - /// This method creates the Root node definition for the tree. - /// Inheritors must override this method to create their own definition. - /// - /// - protected abstract void CreateRootNode(ref XmlTreeNode rootNode); - protected void CreateRootNode() - { - m_initNode = XmlTreeNode.CreateRoot(this); - m_initNode.Icon = FolderIcon; - m_initNode.OpenIcon = FolderIconOpen; - CreateRootNode(ref m_initNode); - } - - - /// - /// This method creates the IAction list for the tree's root node. - /// Inheritors can override this method to create their own Context menu. - /// - /// - protected virtual void CreateRootNodeActions(ref List actions) - { - actions.AddRange(GetDefaultRootNodeActions()); - } - protected void CreateRootNodeActions() - { - CreateRootNodeActions(ref m_initActions); - } - - /// - /// This method creates the AllowedActions IAction list for the tree's nodes. - /// Inheritors can override this method to create their own Context menu. - /// - /// - protected virtual void CreateAllowedActions(ref List actions) - { - actions.Add(ActionDelete.Instance); - - //raise the event, allow developers to modify the collection - var e = new NodeActionsEventArgs(false, actions); - OnNodeActionsCreated(e); - actions = e.AllowedActions; - - } - protected void CreateAllowedActions() - { - CreateAllowedActions(ref m_allowedActions); - } - - /// - /// A helper method to re-generate the root node for the current tree. - /// - /// - public XmlTreeNode GenerateRootNode() - { - XmlTreeNode node = XmlTreeNode.CreateRoot(this); - this.CreateRootNode(ref node); - return node; - } - - /// - /// This method can initialize the ITreeService parameters for this class with another ITreeService object. - /// This method could be used for Dependency Injection. - /// - /// - public void SetTreeParameters(ITreeService treeParams) - { - this.DialogMode = treeParams.DialogMode; - this.NodeKey = treeParams.NodeKey; - this.FunctionToCall = treeParams.FunctionToCall; - this.IsDialog = treeParams.IsDialog; - this.ShowContextMenu = treeParams.ShowContextMenu; - this.id = treeParams.StartNodeID; - - if (!treeParams.ShowContextMenu) - this.RootNode.Menu = null; - } - - /// - /// Returns the tree service url to render the tree - /// - /// - public string GetTreeInitUrl() - { - TreeService treeSvc = new TreeService(this.StartNodeID, TreeAlias, null, null, TreeDialogModes.none, ""); - return treeSvc.GetInitUrl(); - } - - /// - /// Returns the tree service url to return the tree xml structure from the root node - /// - /// - public string GetTreeServiceUrl() - { - return GetTreeServiceUrl(this.StartNodeID); - } - - /// - /// Returns the tree service url to return the tree xml structure from the node passed in - /// - /// - /// - public string GetTreeServiceUrl(int id) - { - // updated by NH to pass showcontextmenu, isdialog and dialogmode variables - TreeService treeSvc = new TreeService(id, TreeAlias, this.ShowContextMenu, this.IsDialog, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the tree service url to return the tree xml structure based on a string node key. - /// - /// - /// - public string GetTreeServiceUrl(string nodeKey) - { - TreeService treeSvc = new TreeService(-1, TreeAlias, this.ShowContextMenu, this.IsDialog, this.DialogMode, "", nodeKey); - return treeSvc.GetServiceUrl(); - } - - - - /// - /// Returns the tree service url to render the tree in dialog mode - /// - /// - public virtual string GetTreeDialogUrl() - { - TreeService treeSvc = new TreeService(this.StartNodeID, TreeAlias, false, true, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the tree service url to render tree xml structure from the node passed in, in dialog mode. - /// - /// - /// - public virtual string GetTreeDialogUrl(int id) - { - TreeService treeSvc = new TreeService(id, TreeAlias, false, true, this.DialogMode, ""); - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the serialized data for the nodeId passed in. - /// - /// - /// This may not work with ITrees that don't support the BaseTree structure with TreeService. - /// If a tree implements other query string data to make it work, this may not function since - /// it only relies on the 3 parameters. - /// - /// - /// - /// - public string GetSerializedNodeData(string nodeId) - { - XmlTree xTree = new XmlTree(); - int id; - if (int.TryParse(nodeId, out id)) - this.id = id; - else - this.NodeKey = nodeId; - - this.Render(ref xTree); - - return xTree.ToString(); - } - - /// - /// Returns the default actions for a root node - /// - /// - public static List GetDefaultRootNodeActions() - { - List actions = new List(); - actions.Add(ActionNew.Instance); - actions.Add(ContextMenuSeperator.Instance); - actions.Add(ActionRefresh.Instance); - return actions; - } - - /// - /// Returns the tree header title. If the alias isn't found in the language files, then it will - /// return the title stored in the umbracoAppTree table. - /// - /// - /// - public static string GetTreeHeader(string alias) - { - string treeCaption = Current.Services.TextService.Localize(alias); - //this is a hack. the tree header title should be in the language files, however, if it is not, we're just - //going to make it equal to what is specified in the db. - if (treeCaption.Length > 0 && treeCaption.Substring(0, 1) == "[") - { - var tree = Current.Services.ApplicationTreeService.GetByAlias(alias); - if (tree != null) - return tree.Title.SplitPascalCasing().ToFirstUpperInvariant(); - } - return treeCaption; - } - - - #region Events - - //These events are poorly designed because they cannot be implemented in the tree inheritance structure, - //it would be up to the individual trees to ensure they launch the events which is not ideal. - //they are also named in appropriately in regards to standards and because everything is by ref, there is no need to - //have 2 events, makes no difference if you want to modify the contents of the data. - public delegate void BeforeNodeRenderEventHandler(ref XmlTree sender, ref XmlTreeNode node, EventArgs e); - public delegate void AfterNodeRenderEventHandler(ref XmlTree sender, ref XmlTreeNode node, EventArgs e); - public static event BeforeNodeRenderEventHandler BeforeNodeRender; - public static event AfterNodeRenderEventHandler AfterNodeRender; - - public static event EventHandler BeforeTreeRender; - public static event EventHandler AfterTreeRender; - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnBeforeNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e) - { - if (sender != null && node != null) - { - if (BeforeNodeRender != null) - BeforeNodeRender(ref sender, ref node, e); - } - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnAfterNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e) - { - if (AfterNodeRender != null) - AfterNodeRender(ref sender, ref node, e); - } - - protected virtual void OnBeforeTreeRender(object sender, TreeEventArgs e) - { - if (BeforeTreeRender != null) - BeforeTreeRender(sender, e); - } - - protected virtual void OnAfterTreeRender(object sender, TreeEventArgs e) - { - if (AfterTreeRender != null) - AfterTreeRender(sender, e); - } - - /// - /// Event that is raised once actions are assigned to nodes - /// - public static event EventHandler NodeActionsCreated; - - protected virtual void OnNodeActionsCreated(NodeActionsEventArgs e) - { - if (NodeActionsCreated != null) - NodeActionsCreated(this, e); - } - - #endregion - - //void IApplicationEventHandler.OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - //} - - //void IApplicationEventHandler.OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - //} - - //void IApplicationEventHandler.OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - //{ - // TreeController.TreeNodesRendering += TreeController_TreeNodesRendering; - //} - - //static void TreeController_TreeNodesRendering(TreeController sender, TreeNodesRenderingEventArgs e) - //{ - // var baseTree = new NullTree(""); - // var legacyTree = new XmlTree(); - // var toRemove = new List(); - - // foreach (var node in e.Nodes) - // { - // //make the legacy node - // var xNode = XmlTreeNode.Create(baseTree); - // xNode.HasChildren = node.HasChildren; - // xNode.IconClass = node.Icon; - // xNode.NodeID = node.NodeId; - // xNode.NodeType = sender.TreeAlias; - // xNode.Text = node.Title; - // xNode.TreeType = sender.TreeAlias; - // //we cannot support this - // //xNode.OpenIcon = node.Icon; - // //xNode.Menu = ?? - - // baseTree.OnBeforeNodeRender(ref legacyTree, ref xNode, new EventArgs()); - // //if the user has nulled this item, then we need to remove it - // if (xNode == null) - // { - // toRemove.Add(node); - // } - // else - // { - // //add to the legacy tree - this mimics what normally happened in legacy trees - // legacyTree.Add(xNode); - - // //now fire the after event - // baseTree.OnAfterNodeRender(ref legacyTree, ref xNode, new EventArgs()); - - // //ok now we need to determine if we need to map any changes back to the real node - // // these are the only properties that can be mapped back. - // node.HasChildren = xNode.HasChildren; - // node.Icon = xNode.IconClass; - // if (xNode.Icon.IsNullOrWhiteSpace() == false) - // { - // node.Icon = xNode.Icon; - // } - // node.NodeType = xNode.NodeType; - // node.Title = xNode.Text; - // } - // } - - // //now remove the nodes that were removed - // foreach (var r in toRemove) - // { - // e.Nodes.Remove(r); - // } - //} - - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs deleted file mode 100644 index bfbbc60f59..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/ITreeService.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace umbraco.cms.presentation.Trees -{ - /// - /// All Trees rely on the properties of an ITreeService interface. This has been created to avoid having trees - /// dependant on the HttpContext - /// - public interface ITreeService - { - /// - /// The NodeKey is a string representation of the nodeID. Generally this is used for tree's whos node's unique key value is a string in instead - /// of an integer such as folder names. - /// - string NodeKey { get; } - int StartNodeID { get; } - bool ShowContextMenu { get; } - bool IsDialog { get; } - TreeDialogModes DialogMode { get; } - string FunctionToCall { get; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs deleted file mode 100644 index 664f076f4c..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NodeActionsEventArgs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - public class NodeActionsEventArgs : EventArgs - { - public NodeActionsEventArgs(bool isRoot, List currActions) - { - AllowedActions = currActions; - IsRoot = isRoot; - } - - public List AllowedActions { get; private set; } - public bool IsRoot { get; private set; } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs deleted file mode 100644 index 1767b4c5e3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/NullTree.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using System.Collections.Generic; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// An empty tree with no functionality. This gets loaded when the requested tree cannot be loaded with the type specified. - /// Should not be used directly in code. - /// - public class NullTree : BaseTree - { - - public NullTree(string application) : base(application) { } - - protected override void CreateRootNodeActions(ref List actions) - { - actions.Clear(); - actions.Add(ActionRefresh.Instance); - } - - public override void RenderJS(ref System.Text.StringBuilder Javascript) { } - - public override void Render(ref XmlTree tree) - { - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.Text = "Error"; - xNode.Menu = null; - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.Menu = null; - rootNode.Text = "Error"; - } - - public override string TreeAlias - { - get - { - return "NullTree"; - } - } - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs deleted file mode 100644 index 5855775b9a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinition.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using Umbraco.Core.Models; - -namespace umbraco.cms.presentation.Trees -{ - - /// - /// Defines the entire structure of an application tree including it's Type, a reference to it's ApplicationTree object, and a reference - /// to it's Application object. Tree Definitions are based on defining a database in the umbracoAppTree database. Any tree defined in this table - /// that is of an ITree type, it will be found and can be instantiated by this class. Any ITree that is not defined in the database will - /// need to be instantiated with it's own tree constructor. - /// - public class TreeDefinition - { - /// - /// Initializes a new instance of the class. - /// - /// The type. - /// The tree. - /// The app. - public TreeDefinition(Type type, ApplicationTree tree, Section app) - { - m_treeType = type; - m_tree = tree; - m_app = app; - } - - private Type m_treeType; - private ApplicationTree m_tree; - private Section m_app; - - /// - /// Returns a new instance of a BaseTree based on this Tree Definition - /// - public BaseTree CreateInstance() - { - //create the tree instance - var typeInstance = CreateTreeInstance(m_treeType, m_app.Alias); - - if (typeInstance != null) - { - //convert to BaseTree - return typeInstance; - } - return null; - } - - /// - /// Gets or sets the type of the tree. - /// - /// The type of the tree. - public Type TreeType - { - get { return m_treeType; } - set { m_treeType = value; } - } - - /// - /// Gets or sets the tree. - /// - /// The tree. - public ApplicationTree Tree - { - get { return m_tree; } - set { m_tree = value; } - } - - /// - /// Gets or sets the application. - /// - /// The app. - public Section App - { - get { return m_app; } - set { m_app = value; } - } - - /// - /// Creates an ITree instance. - /// - /// The tree. - /// The app alias. - /// - public static BaseTree CreateTreeInstance(Type tree, string appAlias) - { - BaseTree typeInstance; - //call the correct constructor - if (typeof(BaseTree).IsAssignableFrom(tree)) - typeInstance = Activator.CreateInstance(tree, new object[] { appAlias }) as BaseTree; //the BaseTree constructor - else - typeInstance = CreateTreeInstance(tree); - return typeInstance; - } - - private static BaseTree CreateTreeInstance(Type tree) - { - BaseTree typeInstance = Activator.CreateInstance(tree) as BaseTree; //an empty constructor (ITree) - return typeInstance; - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs deleted file mode 100644 index 9157080597..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDefinitionCollection.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Web; -using Umbraco.Web.Composing; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// A collection of TreeDefinitions found in any loaded assembly. - /// - public class TreeDefinitionCollection : List - { - - //create singleton - private static readonly TreeDefinitionCollection instance = new TreeDefinitionCollection(); - - private static readonly object Locker = new object(); - private static volatile bool _ensureTrees = false; - - public static TreeDefinitionCollection Instance - { - get - { - instance.EnsureTreesRegistered(); - return instance; - } - } - - /// - /// Find the TreeDefinition object based on the ITree - /// - /// - /// - public TreeDefinition FindTree(BaseTree tree) - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - t => t.TreeType == tree.GetType() - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Finds the TreeDefinition with the generic type specified - /// - /// - /// - public TreeDefinition FindTree() where T : BaseTree - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - delegate(TreeDefinition t) - { - // zb-00002 #29929 : use IsAssignableFrom instead of Equal, otherwise you can't override built-in - // trees because for ex. PermissionEditor.aspx.cs OnInit calls FindTree() - return typeof(T).IsAssignableFrom(t.TreeType); - } - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Return the TreeDefinition object based on the tree alias and application it belongs to - /// - /// - /// - public TreeDefinition FindTree(string alias) - { - EnsureTreesRegistered(); - - var foundTree = this.Find( - t => t.Tree.Alias.ToLower() == alias.ToLower() - ); - if (foundTree != null) - return foundTree; - - return null; - } - - /// - /// Return a list of TreeDefinition's with the appAlias specified - /// - /// - /// - public List FindTrees(string appAlias) - { - EnsureTreesRegistered(); - - return this.FindAll( - tree => (tree.App != null && tree.App.Alias.ToLower() == appAlias.ToLower()) - ); - } - - /// - /// Return a list of TreeDefinition's with the appAlias specified - /// - /// - /// - public List FindActiveTrees(string appAlias) - { - EnsureTreesRegistered(); - - return this.FindAll( - tree => (tree.App != null && tree.App.Alias.ToLower() == appAlias.ToLower() && tree.Tree.Initialize) - ); - } - - public void ReRegisterTrees() - { - //clears the trees/flag so that they are lazily refreshed on next access - lock (Locker) - { - this.Clear(); - _ensureTrees = false; - } - } - - /// - /// Finds all instances of ITree in loaded assemblies, then finds their associated ApplicationTree and Application objects - /// and stores them together in a TreeDefinition class and adds the definition to our list. - /// This will also store an instance of each tree object in the TreeDefinition class which should be - /// used when referencing all tree classes. - /// - private void EnsureTreesRegistered() - { - if (_ensureTrees == false) - { - lock (Locker) - { - if (_ensureTrees == false) - { - - var foundITrees = Current.TypeLoader.GetTrees(); - - var appTrees = Current.Services.ApplicationTreeService.GetAll().ToList(); - var apps = Current.Services.SectionService.GetSections().ToList(); - - foreach (var type in foundITrees) - { - - //find the Application tree's who's combination of assembly name and tree type is equal to - //the Type that was found's full name. - //Since a tree can exist in multiple applications we'll need to register them all. - - //The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360 - // we will support the old legacy way but the normal way is to match on assembly qualified names - - var appTreesForType = appTrees.FindAll( - tree => - { - //match the type on assembly qualified name - return tree.GetRuntimeType() == type; - } - ); - - foreach (var appTree in appTreesForType) - { - //find the Application object whos name is the same as our appTree ApplicationAlias - var app = apps.Find( - a => (a.Alias == appTree.ApplicationAlias) - ); - - var def = new TreeDefinition(type, appTree, app); - this.Add(def); - } - } - //sort our trees with the sort order definition - this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder)); - - _ensureTrees = true; - } - } - } - - - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs deleted file mode 100644 index fab8545d76..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeDialogModes.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; - -namespace umbraco.cms.presentation.Trees -{ - public enum TreeDialogModes - { - none,id,locallink,fulllink - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs deleted file mode 100644 index 2f6f4ecf5e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeEventArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - - -namespace umbraco.cms.presentation.Trees -{ - /// - /// - /// - public class TreeEventArgs : EventArgs - { - /// - /// Gets the tree. - /// - /// The tree. - public XmlTree Tree { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The tree which the event is for. - public TreeEventArgs(XmlTree tree) - { - this.Tree = tree; - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs deleted file mode 100644 index 9f28a472d0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeRequestParams.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// An ITreeService class that returns the values found in the Query String or any dictionary - /// - internal class TreeRequestParams : ITreeService - { - private TreeRequestParams() { } - - private Dictionary m_params; - - public static TreeRequestParams FromQueryStrings() - { - Dictionary p = new Dictionary(); - foreach (string key in HttpContext.Current.Request.QueryString.Keys) - { - p.Add(key, HttpUtility.HtmlEncode(HttpContext.Current.Request.QueryString[key])); - //p.Add(item.Key.ToString(), item.Value.ToString()); - } - return FromDictionary(p); - } - - public static TreeRequestParams FromDictionary(Dictionary items) - { - TreeRequestParams treeParams = new TreeRequestParams(); - treeParams.m_params = items; - return treeParams; - } - - /// - /// Converts the tree parameters to a tree service object - /// - /// - public TreeService CreateTreeService() - { - return new TreeService() - { - ShowContextMenu = this.ShowContextMenu, - IsDialog = this.IsDialog, - DialogMode = this.DialogMode, - App = this.Application, - TreeType = this.TreeType, - NodeKey = this.NodeKey, - StartNodeID = this.StartNodeID, - FunctionToCall = this.FunctionToCall - }; - } - - public string NodeKey - { - get - { - return (m_params.ContainsKey("nodeKey") ? m_params["nodeKey"] : ""); - } - } - public string Application - { - get - { - return (m_params.ContainsKey("app") ? m_params["app"] : m_params.ContainsKey("appAlias") ? m_params["appAlias"] : ""); - } - } - public int StartNodeID - { - get - { - if(m_params.ContainsKey("id")) - { - int sNodeID; - if (int.TryParse(m_params["id"], out sNodeID)) - return sNodeID; - } - return -1; - } - } - public string FunctionToCall - { - get - { - return (m_params.ContainsKey("functionToCall") ? m_params["functionToCall"] : ""); - } - } - public bool IsDialog - { - get - { - bool value; - if (m_params.ContainsKey("isDialog")) - if (bool.TryParse(m_params["isDialog"], out value)) - return value; - return false; - } - } - public TreeDialogModes DialogMode - { - get - { - if (m_params.ContainsKey("dialogMode") && IsDialog) - { - try - { - return (TreeDialogModes)Enum.Parse(typeof(TreeDialogModes), m_params["dialogMode"]); - } - catch - { - return TreeDialogModes.none; - } - } - return TreeDialogModes.none; - } - } - public bool ShowContextMenu - { - get - { - bool value; - if (m_params.ContainsKey("contextMenu")) - if (bool.TryParse(m_params["contextMenu"], out value)) - return value; - return true; - } - } - public string TreeType - { - get - { - return (m_params.ContainsKey("treeType") ? m_params["treeType"] : ""); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs deleted file mode 100644 index 6a4df9ea0a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Text; -using Umbraco.Web._Legacy.Controls; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// A utility class to aid in creating the URL for returning XML for a tree structure and - /// for reading the parameters from the URL when a request is made. - /// - public class TreeService : TreeUrlGenerator, ITreeService - { - - /// - /// Default empty constructor - /// - public TreeService() : base() { } - - /// - /// Constructor to assign all TreeService properties except nodeKey in one call - /// - /// - /// - /// - /// - /// - /// - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - } - - /// - /// Constructor to assign all TreeService properties in one call - /// - /// - /// - /// - /// - /// - /// - /// - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - NodeKey = nodeKey; - } - - public TreeService(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - StartNodeID = startNodeID ?? -1; - TreeType = treeType; - ShowContextMenu = showContextMenu ?? true; - IsDialog = isDialog ?? false; - m_dialogMode = dialogMode; - App = app; - NodeKey = nodeKey; - FunctionToCall = functionToCall; - } - - private TreeDialogModes m_dialogMode; - - #region Public Properties - - - public TreeDialogModes DialogMode - { - get { return m_dialogMode; } - set { m_dialogMode = value; } - } - - #endregion - - /// - /// Static method to return the tree service url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetServiceUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - TreeService treeSvc = new TreeService(startNodeID, treeType, showContextMenu, isDialog, dialogMode, app, nodeKey, functionToCall); - return treeSvc.GetServiceUrl(); - } - - /// - /// static method to return the tree init url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetInitUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, TreeDialogModes dialogMode, string app, string nodeKey, string functionToCall) - { - TreeService treeSvc = new TreeService(startNodeID, treeType, showContextMenu, isDialog, dialogMode, app, nodeKey, functionToCall); - return treeSvc.GetInitUrl(); - } - - protected override string GetUrl(string pageUrl) - { - StringBuilder sb = new StringBuilder(base.GetUrl(pageUrl)); - if (this.DialogMode != TreeDialogModes.none) sb.Append(string.Format("&dialogMode={0}", this.DialogMode.ToString())); - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs deleted file mode 100644 index 277c6270a1..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/TreeUrlGenerator.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Text; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// This class will generate the URLs for iframe tree pages. - /// Generally used to get the a tree picker url. - /// - /// - /// This was created in 4.1 so that this helper class can be exposed to other assemblies since - /// it only existed in the presentation assembly in previous versions - /// - public class TreeUrlGenerator - { - - public const string TREE_URL = "tree.aspx"; - public const string INIT_URL = "treeinit.aspx"; - public const string PICKER_URL = "treepicker.aspx"; - - private int? m_startNodeID; - private string m_treeType; - private bool? m_showContextMenu; - private bool? m_isDialog; - private string m_app; - private string m_nodeKey; - private string m_functionToCall; - - #region Public Properties - - public string FunctionToCall - { - get { return m_functionToCall; } - set { m_functionToCall = value; } - } - - public string NodeKey - { - get { return m_nodeKey; } - set { m_nodeKey = value; } - } - - public int StartNodeID - { - get { return m_startNodeID ?? -1; } - set { m_startNodeID = value; } - } - - public string TreeType - { - get { return m_treeType; } - set { m_treeType = value; } - } - - public bool ShowContextMenu - { - get { return m_showContextMenu ?? true; } - set { m_showContextMenu = value; } - } - - public bool IsDialog - { - get { return m_isDialog ?? false; } - set { m_isDialog = value; } - } - - public string App - { - get { return m_app; } - set { m_app = value; } - } - #endregion - - /// - /// Returns the url for servicing the xml tree request based on the parameters specified on this class. - /// - /// Tree service url as a string - public string GetServiceUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/" + GetUrl(TREE_URL); - } - - /// - /// Static method to return the tree service url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetServiceUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, string app, string nodeKey, string functionToCall) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator() - { - StartNodeID = startNodeID ?? -1, - TreeType = treeType, - ShowContextMenu = showContextMenu ?? true, - IsDialog = isDialog ?? false, - App = app, - NodeKey = nodeKey, - FunctionToCall = functionToCall - }; - return treeSvc.GetServiceUrl(); - } - - /// - /// Returns the url for initializing the tree based on the parameters specified on this class - /// - /// - public string GetInitUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/" + GetUrl(INIT_URL); - } - - /// - /// static method to return the tree init url with the specified parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static string GetInitUrl(int? startNodeID, string treeType, bool? showContextMenu, - bool? isDialog, string app, string nodeKey, string functionToCall) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator() - { - StartNodeID = startNodeID ?? -1, - TreeType = treeType, - ShowContextMenu = showContextMenu ?? true, - IsDialog = isDialog ?? false, - App = app, - NodeKey = nodeKey, - FunctionToCall = functionToCall - }; - return treeSvc.GetInitUrl(); - } - - /// - /// Returns the url for the tree picker (used on modal windows) based on the parameters specified on this class - /// - public static string GetPickerUrl(string app, string treeType) - { - TreeUrlGenerator treeSvc = new TreeUrlGenerator(); - treeSvc.App = app; - treeSvc.TreeType = treeType; - return treeSvc.GetPickerUrl(); - } - - /// - /// Returns the url for the tree picker (used on modal windows) based on the parameters specified on this class - /// - public string GetPickerUrl() - { - return Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) + "/dialogs/" + GetUrl(PICKER_URL); - } - - /// - /// Generates the URL parameters for the tree service. - /// - /// the base url (i.e. tree.aspx) - /// - protected virtual string GetUrl(string pageUrl) - { - StringBuilder sb = new StringBuilder(); - - sb.Append(pageUrl); - //insert random - sb.Append(string.Format("?rnd={0}", Guid.NewGuid().ToString("N"))); - - sb.Append(string.Format("&id={0}", this.StartNodeID.ToString())); - if (!string.IsNullOrEmpty(this.TreeType)) sb.Append(string.Format("&treeType={0}", this.TreeType)); - if (!string.IsNullOrEmpty(this.NodeKey)) sb.Append(string.Format("&nodeKey={0}", this.NodeKey)); - sb.Append(string.Format("&contextMenu={0}", this.ShowContextMenu.ToString().ToLower())); - sb.Append(string.Format("&isDialog={0}", this.IsDialog.ToString().ToLower())); - if (!string.IsNullOrEmpty(this.App)) sb.Append(string.Format("&app={0}", this.App)); - - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs deleted file mode 100644 index 56f794caa0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System.Xml.Serialization; -using System.Collections; -using System; -using System.Xml.Schema; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Web.Composing; -using Umbraco.Web.UI.Pages; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco.cms.presentation.Trees -{ - /// - /// Used for serializing data to XML as the data structure for the JavaScript tree - /// - [XmlRoot(ElementName = "tree", IsNullable = false), Serializable] - public class XmlTree - { - - public XmlTree() - { - Init(); - } - - private void Init() - { - } - - public void Add(XmlTreeNode obj) - { - treeCollection.Add(obj); - } - - [XmlIgnore] - public XmlTreeNode this[int index] - { - get { return (XmlTreeNode)treeCollection[index]; } - } - - [XmlIgnore] - public int Count - { - get { return treeCollection.Count; } - } - - public void Clear() - { - treeCollection.Clear(); - } - - public XmlTreeNode Remove(int index) - { - XmlTreeNode obj = treeCollection[index]; - treeCollection.Remove(obj); - return obj; - } - - public void Remove(XmlTreeNode obj) - { - treeCollection.Remove(obj); - } - - - private List __treeCollection; - - [XmlElement(Type = typeof(XmlTreeNode), ElementName = "tree", IsNullable = false, Form = XmlSchemaForm.Qualified)] - public List treeCollection - { - get - { - if (__treeCollection == null) __treeCollection = new List(); - return __treeCollection; - } - set { __treeCollection = value; } - } - - [System.Runtime.InteropServices.DispIdAttribute(-4)] - public IEnumerator GetEnumerator() - { - return (treeCollection as IEnumerable).GetEnumerator(); - } - - } - - /// - /// Used for serializing data to XML as the data structure for the JavaScript tree - /// - [Serializable] - public class XmlTreeNode : IXmlSerializable - { - - private XmlTreeNode() - { - m_nodeStyle = new NodeStyle(); - } - - /// - /// creates a new XmlTreeNode with the default parameters from the BaseTree - /// - /// - /// - public static XmlTreeNode Create(BaseTree bTree) - { - XmlTreeNode xNode = new XmlTreeNode(); - xNode.Menu = bTree.AllowedActions.FindAll(delegate(IAction a) { return true; }); //return a duplicate copy of the list - xNode.NodeType = bTree.TreeAlias; - xNode.Source = string.Empty; - xNode.IsRoot = false; - //generally the tree type and node type are the same but in some cased they are not. - xNode.m_treeType = bTree.TreeAlias; - return xNode; - } - - /// - /// creates a new XmlTreeNode with the default parameters for the BaseTree root node - /// - /// - /// - public static XmlTreeNode CreateRoot(BaseTree bTree) - { - XmlTreeNode xNode = new XmlTreeNode(); - xNode.NodeID = bTree.StartNodeID.ToString(); - xNode.Source = bTree.GetTreeServiceUrl(); - xNode.Menu = bTree.RootNodeActions.FindAll(delegate(IAction a) { return true; }); //return a duplicate copy of the list - xNode.NodeType = bTree.TreeAlias; - xNode.Text = BaseTree.GetTreeHeader(bTree.TreeAlias); - - // The apps dashboard action will be used - xNode.Action = "javascript:" + ClientTools.Scripts.OpenDashboard(bTree.app); - - xNode.IsRoot = true; - //generally the tree type and node type are the same but in some cased they are not. - xNode.m_treeType = bTree.TreeAlias; - return xNode; - } - - private NodeStyle m_nodeStyle; - - private bool? m_notPublished; - private bool? m_isProtected; - private List m_menu; - private string m_text; - private string m_action; - [Obsolete("This is never used. From version 3 and below probably")] - private string m_rootSrc; - private string m_src; - private string m_iconClass = ""; - private string m_icon; - private string m_openIcon; - private string m_nodeType; - private string m_nodeID; - private string m_treeType; - - /// - /// Set to true when a node is created with CreateRootNode - /// - internal bool IsRoot { get; private set; } - - /// - /// Generally the tree type and node type are the same but in some cased they are not so - /// we need to store the tree type too which is read only. - /// - public string TreeType - { - get { return m_treeType; } - internal set { m_treeType = value; } - } - - public bool HasChildren - { - get - { - return m_HasChildren ?? !string.IsNullOrEmpty(this.Source); //defaults to true if source is specified - } - set - { - m_HasChildren = value; - } - } - private bool? m_HasChildren = null; - - public string NodeID - { - get { return m_nodeID; } - set { m_nodeID = value; } - } - - /// - /// The tree node text - /// - public string Text - { - get { return m_text; } - set { m_text = value; } - } - - /// - /// The CSS class of the icon to use for the node - /// - public string IconClass - { - get { return m_iconClass; } - set { m_iconClass = value; } - } - - /// - /// The JavaScript action for the node - /// - public string Action - { - get { return m_action; } - set { m_action = value; } - } - - /// - /// A string of letters representing actions for the context menu - /// - public List Menu - { - get { return m_menu; } - set { m_menu = value; } - } - - /// - /// The xml source for the child nodes (a URL) - /// - public string Source - { - get { return m_src; } - set { m_src = value; } - } - - /// - /// The path to the icon to display for the node - /// - public string Icon - { - get { return m_icon; } - set { m_icon = value; } - } - - /// - /// The path to the icon to display for the node if the node is showing it's children - /// - public string OpenIcon - { - get { return m_openIcon; } - set { m_openIcon = value; } - } - - /// - /// Normally just the type of tree being rendered. - /// This should really only be set with this property in very special cases - /// where the create task for a node in the same tree as another node is different. - /// - public string NodeType - { - get { return m_nodeType; } - set { m_nodeType = value; } - } - - - - /// - /// Returns the styling object used to add common styles to a node - /// - public NodeStyle Style - { - get { return m_nodeStyle; } - } - - /// - /// Used to add common styles to an XmlTreeNode. - /// This also adds the ability to add a custom class which will add the class to the li node - /// that is rendered in the tree whereas the IconClass property of the XmlTreeNode object - /// adds a class to the anchor of the li node. - /// - public sealed class NodeStyle - { - internal NodeStyle() - { - AppliedClasses = new List(); - } - - private const string DimNodeCssClass = "not-published"; - private const string HighlightNodeCssClass = "has-unpublished-version"; - private const string SecureNodeCssClass = "protected"; - - internal List AppliedClasses { get; private set; } - - /// - /// Dims the color of the node - /// - public void DimNode() - { - if (!AppliedClasses.Contains(DimNodeCssClass)) - AppliedClasses.Add(DimNodeCssClass); - } - - /// - /// Adds the star icon highlight overlay to a node - /// - public void HighlightNode() - { - if (!AppliedClasses.Contains(HighlightNodeCssClass)) - AppliedClasses.Add(HighlightNodeCssClass); - } - - /// - /// Adds the padlock icon overlay to a node - /// - public void SecureNode() - { - if (!AppliedClasses.Contains(SecureNodeCssClass)) - AppliedClasses.Add(SecureNodeCssClass); - } - - /// - /// Adds a custom class to the li node of the tree - /// - /// - public void AddCustom(string cssClass) - { - if (!AppliedClasses.Contains(cssClass)) - AppliedClasses.Add(cssClass); - } - } - - - #region IXmlSerializable Members - - public XmlSchema GetSchema() - { - return null; - } - - public void ReadXml(System.Xml.XmlReader reader) - { - if (reader.HasAttributes) - { - while (reader.MoveToNextAttribute()) - { - //try to parse the name into enum - TreeAttributes current; - try - { - current = (TreeAttributes)Enum.Parse(typeof(TreeAttributes), reader.Name, true); - } - catch - { - break; - } - switch (current) - { - case TreeAttributes.nodeID: - this.m_nodeID = reader.Value; - break; - case TreeAttributes.text: - this.m_text = reader.Value; - break; - case TreeAttributes.iconClass: - this.m_iconClass = reader.Value; - break; - case TreeAttributes.action: - this.m_action = reader.Value; - break; - case TreeAttributes.menu: - this.m_menu = !string.IsNullOrEmpty(reader.Value) - ? FromActionSymbols(reader.Value.Split(',')).ToList() - : null; - break; - case TreeAttributes.rootSrc: - this.m_rootSrc = reader.Value; - break; - case TreeAttributes.src: - this.m_src = reader.Value; - break; - case TreeAttributes.icon: - this.m_icon = reader.Value; - break; - case TreeAttributes.openIcon: - this.m_openIcon = reader.Value; - break; - case TreeAttributes.nodeType: - this.m_nodeType = reader.Value; - break; - case TreeAttributes.notPublished: - if (!string.IsNullOrEmpty(reader.Value)) this.m_notPublished = bool.Parse(reader.Value); - break; - case TreeAttributes.isProtected: - if (!string.IsNullOrEmpty(reader.Value)) this.m_isProtected = bool.Parse(reader.Value); - break; - case TreeAttributes.hasChildren: - if (!string.IsNullOrEmpty(reader.Value)) this.HasChildren = bool.Parse(reader.Value); - break; - } - } - //need to set the hasChildren property if it is null but there is a source - //this happens when the hasChildren attribute is not set because the developer didn't know it was there - //only occurs for ITree obviously - if (!this.HasChildren && !string.IsNullOrEmpty(this.m_src)) - this.HasChildren = true; - - } - - reader.Read(); - } - - public void WriteXml(System.Xml.XmlWriter writer) - { - writer.WriteAttributeString(TreeAttributes.nodeID.ToString(), m_nodeID); - writer.WriteAttributeString(TreeAttributes.text.ToString(), m_text); - writer.WriteAttributeString(TreeAttributes.iconClass.ToString(), m_iconClass); - writer.WriteAttributeString(TreeAttributes.action.ToString(), m_action); - writer.WriteAttributeString(TreeAttributes.menu.ToString(), m_menu != null && m_menu.Count > 0 ? string.Join(",", ToActionSymbols(m_menu)) : ""); - writer.WriteAttributeString(TreeAttributes.rootSrc.ToString(), m_rootSrc); - writer.WriteAttributeString(TreeAttributes.src.ToString(), m_src); - writer.WriteAttributeString(TreeAttributes.icon.ToString(), m_icon); - writer.WriteAttributeString(TreeAttributes.openIcon.ToString(), m_openIcon); - writer.WriteAttributeString(TreeAttributes.nodeType.ToString(), m_nodeType); - writer.WriteAttributeString(TreeAttributes.hasChildren.ToString(), HasChildren.ToString().ToLower()); - if (m_notPublished.HasValue) writer.WriteAttributeString(TreeAttributes.notPublished.ToString(), m_notPublished.Value.ToString().ToLower()); - if (m_isProtected.HasValue) writer.WriteAttributeString(TreeAttributes.isProtected.ToString(), m_isProtected.Value.ToString().ToLower()); - } - - internal enum TreeAttributes - { - nodeID, text, iconClass, action, menu, rootSrc, src, icon, openIcon, nodeType, notPublished, isProtected, hasChildren - } - - #endregion - - /// - /// This method will return a list of IAction's based on a string (letter) list. Each character in the list may represent - /// an IAction. This will associate any found IActions based on the Letter property of the IAction with the character being referenced. - /// - /// - /// returns a list of actions that have an associated letter found in the action string list - private IEnumerable FromActionSymbols(IEnumerable actions) - { - return Current.Actions.GetByLetters(actions); - } - - /// - /// Returns the string (letter) representation of the actions that make up the actions collection - /// - /// - private IEnumerable ToActionSymbols(IEnumerable actions) - { - return actions.Select(x => x.Letter.ToString(CultureInfo.InvariantCulture)).ToArray(); - } - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs deleted file mode 100644 index d07251ea7a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/installer.aspx.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Web.UI; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Web; -using Umbraco.Web.UI.JavaScript; -using Umbraco.Web._Legacy.Controls; -using Umbraco.Web.UI.Pages; - -namespace umbraco.presentation.developer.packages -{ - /// - /// Summary description for packager. - /// - [Obsolete("This should not be used and will be removed in v8, this is kept here only for backwards compat reasons, this page should never be rendered/used")] - public class Installer : UmbracoEnsuredPage - { - - private Control _configControl; - private readonly cms.businesslogic.packager.Installer _installer; - protected Pane pane_installing; - protected Pane pane_optional; - - public Installer() - { - CurrentApp = Constants.Applications.Packages; - _installer = new cms.businesslogic.packager.Installer(Security.CurrentUser.Id); - } - - protected void Page_Load(object sender, EventArgs e) - { - if (string.IsNullOrEmpty(Request.GetItemAsString("installing"))) - return; - - pane_optional.Visible = false; - pane_installing.Visible = true; - ProcessInstall(Request.GetItemAsString("installing")); - } - - private void ProcessInstall(string currentStep) - { - var dir = Request.GetItemAsString("dir"); - - int.TryParse(Request.GetItemAsString("pId"), out var packageId); - - switch (currentStep.ToLowerInvariant()) - { - case "custominstaller": - var customControl = Request.GetItemAsString("customControl"); - - if (customControl.IsNullOrWhiteSpace() == false) - { - pane_optional.Visible = false; - - _configControl = LoadControl(SystemDirectories.Root + customControl); - _configControl.ID = "packagerConfigControl"; - - pane_optional.Controls.Add(_configControl); - pane_optional.Visible = true; - - if (IsPostBack == false) - { - //We still need to clean everything up which is normally done in the Finished Action - PerformPostInstallCleanup(packageId, dir); - } - - } - else - { - //if the custom installer control is empty here (though it should never be because we've already checked for it previously) - //then we should run the normal FinishedAction - PerformFinishedAction(packageId, dir); - } - break; - default: - break; - } - } - - private void PerformPostInstallCleanup(int packageId, string dir) - { - _installer.InstallCleanUp(packageId, dir); - - // Update ClientDependency version - var clientDependencyConfig = new ClientDependencyConfiguration(Logger); - var clientDependencyUpdated = clientDependencyConfig.UpdateVersionNumber( - UmbracoVersion.SemanticVersion, DateTime.UtcNow, "yyyyMMdd"); - - //clear the tree cache - we'll do this here even though the browser will reload, but just in case it doesn't can't hurt. - ClientTools.ClearClientTreeCache().RefreshTree("packager"); - TreeDefinitionCollection.Instance.ReRegisterTrees(); - } - - private void PerformFinishedAction(int packageId, string dir) - { - pane_optional.Visible = false; - PerformPostInstallCleanup(packageId, dir); - } - } -} From a100d321f67ec2640124a5922684f7613d09396f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Oct 2018 18:08:57 +1100 Subject: [PATCH 146/278] Fixes group node route --- .../common/directives/components/tree/umbtree.directive.js | 2 ++ src/Umbraco.Web/Models/Trees/SectionRootNode.cs | 5 +++-- src/Umbraco.Web/Trees/ApplicationTreeController.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 3212690bbb..836a5af2a6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -80,6 +80,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use // entire tree again since we already still have it in memory. Of course if the section is different we will // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times // since it saves on data retreival and DOM processing. + //TODO: This isn't used!? var lastSection = ""; /** Helper function to emit tree events */ @@ -91,6 +92,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } + //TODO: This isn't used!? function clearCache(section) { treeService.clearCache({ section: section }); } diff --git a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs index 4265cbaa7f..cd4fc3e483 100644 --- a/src/Umbraco.Web/Models/Trees/SectionRootNode.cs +++ b/src/Umbraco.Web/Models/Trees/SectionRootNode.cs @@ -34,12 +34,13 @@ namespace Umbraco.Web.Models.Trees ///
    /// /// - public static TreeRootNode CreateGroupNode(TreeNodeCollection children) + public static TreeRootNode CreateGroupNode(TreeNodeCollection children, string section) { var sectionRoot = new TreeRootNode(RootId, string.Empty, string.Empty) { IsGroup = true, - Children = children + Children = children, + RoutePath = section }; return sectionRoot; diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index b4d217fe62..fd669a1b07 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -133,7 +133,7 @@ namespace Umbraco.Web.Trees if (groupNodeCollection.Count > 0) { - var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection); + var groupRoot = TreeRootNode.CreateGroupNode(groupNodeCollection, application); groupRoot.Name = Services.TextService.Localize("treeHeaders/" + treeGroupName); rootNodeGroups.Add(groupRoot); From f868172f484c968e4036658761abae3771aeb3ec Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Oct 2018 10:58:17 +0200 Subject: [PATCH 147/278] Cleanup --- .../PublishedContent/PublishedModelFactory.cs | 4 ++-- src/Umbraco.Core/ReflectionUtilities.cs | 18 +++++++-------- .../CtorInvokeBenchmarks.cs | 2 +- .../Clr/ReflectionUtilitiesTests.cs | 22 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index c72a89c1f2..67758c1c69 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -71,7 +71,7 @@ namespace Umbraco.Core.Models.PublishedContent throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); // have to use an unsafe ctor because we don't know the types, really - var modelCtor = ReflectionUtilities.EmitCtorUnsafe>(constructor); + var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; modelTypeMap[typeName] = type; } @@ -112,7 +112,7 @@ namespace Umbraco.Core.Models.PublishedContent if (ctor != null) return ctor(); var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitCtor>(declaring: listType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstuctor>(declaring: listType); return ctor(); } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index a5acfe78e3..870cb9ec13 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -295,7 +295,7 @@ namespace Umbraco.Core /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. - public static TLambda EmitCtor(bool mustExist = true, Type declaring = null) + public static TLambda EmitConstuctor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); @@ -313,7 +313,7 @@ namespace Umbraco.Core } // emit - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } /// @@ -325,16 +325,16 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtor(ConstructorInfo ctor) + public static TLambda EmitConstructor(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); - return EmitCtorSafe(lambdaParameters, lambdaReturned, ctor); + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } - private static TLambda EmitCtorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) + private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) { // get type and args var ctorDeclaring = ctor.DeclaringType; @@ -350,7 +350,7 @@ namespace Umbraco.Core ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); // emit - return EmitCtor(ctorDeclaring, ctorParameters, ctor); + return EmitConstructor(ctorDeclaring, ctorParameters, ctor); } /// @@ -367,17 +367,17 @@ namespace Umbraco.Core /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. - public static TLambda EmitCtorUnsafe(ConstructorInfo ctor) + public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); // emit - unsafe - use lambda's args and assume they are correct - return EmitCtor(lambdaReturned, lambdaParameters, ctor); + return EmitConstructor(lambdaReturned, lambdaParameters, ctor); } - private static TLambda EmitCtor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) + private static TLambda EmitConstructor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) { // gets the method argument types var ctorParameters = GetParameters(ctor); diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 4855b161df..1d5876187b 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Benchmarks // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( - _emittedCtor = ReflectionUtilities.EmitCtor>(); + _emittedCtor = ReflectionUtilities.EmitConstuctor>(); } public IFoo IlCtor(IFoo foo) diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs index 1f7f164b21..46dae8bcfd 100644 --- a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -13,16 +13,16 @@ namespace Umbraco.Tests.Clr [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitCtor>(); + var ctor1 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class1)); + var ctor2 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitCtor>(); + var ctor3 = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitCtor>(declaring: typeof(Class3)); + var ctor4 = ReflectionUtilities.EmitConstuctor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } @@ -30,40 +30,40 @@ namespace Umbraco.Tests.Clr public void EmitCtorEmitsFromInfo() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - var ctor1 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor1 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor1()); ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new[] { typeof(int) }, null); - var ctor3 = ReflectionUtilities.EmitCtor>(ctorInfo); + var ctor3 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor3(42)); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitCtor>(); + var ctor = ReflectionUtilities.EmitConstuctor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] public void EmitCtorThrowsIfNotFound() { - Assert.Throws(() => ReflectionUtilities.EmitCtor>()); + Assert.Throws(() => ReflectionUtilities.EmitConstuctor>()); } [Test] public void EmitCtorThrowsIfInvalid() { var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - Assert.Throws(() => ReflectionUtilities.EmitCtor>(ctorInfo)); + Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] public void EmitCtorReturnsNull() { - Assert.IsNull(ReflectionUtilities.EmitCtor>(false)); + Assert.IsNull(ReflectionUtilities.EmitConstuctor>(false)); } [Test] From 3d52f2676ed1cab1602a6f8129187162d0e3f920 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 10:59:04 +0200 Subject: [PATCH 148/278] Add possibility to extend the umb-box-header + add rollback button --- .../content/umbcontentnodeinfo.directive.js | 4 ++++ .../src/less/components/umb-box.less | 3 +++ .../content/umb-content-node-info.html | 12 +++++++++++- .../html/umb-box/umb-box-header.html | 19 +++++++++++-------- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index b2e64983d6..d274cb495c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -101,6 +101,10 @@ scope.node.template = templateAlias; }; + scope.openRollback = function() { + console.log("rollback"); + }; + function loadAuditTrail() { scope.loadingAuditTrail = true; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index f2cacc26b3..fb83504a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -8,6 +8,9 @@ .umb-box-header { padding: 10px 20px; border-bottom: 1px solid @gray-9; + display: flex; + align-items: center; + justify-content: space-between; } .umb-box-header-title { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 03d4318ba2..9849ba0c08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,17 @@ - + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html index 8c59061788..e9f771f09c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html @@ -1,10 +1,13 @@ -
    -
    - - {{title}} -
    -
    - - {{description}} +
    +
    +
    + + {{title}} +
    +
    + + {{description}} +
    +
    \ No newline at end of file From eceec9ea3e431fc02e6a33e8b3470361b1c2dd33 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 11:43:14 +0200 Subject: [PATCH 149/278] Set rollback button to be xs + fix padding on xs buttons --- .../src/less/components/buttons/umb-button.less | 2 +- .../src/views/components/content/umb-content-node-info.html | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4b670ab781..d3f2fee5d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -104,7 +104,7 @@ } .umb-button--xs { - padding: 5px 16px; + padding: 5px 13px; font-size: 14px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 9849ba0c08..77a7161762 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -48,7 +48,8 @@ type="button" button-style="outline" action="openRollback()" - label-key="actions_rollback"> + label-key="actions_rollback" + size="xs"> From 982fb46e7dd38a75c981c39cf747d9d7e00f51bd Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 16 Oct 2018 11:16:45 +0100 Subject: [PATCH 150/278] Fix failing unit test - number of trees gone down as we have removed the user tree (as this is now a fullscreen/wide section/app only) --- src/Umbraco.Tests/Composing/TypeFinderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index a8624e8871..955f6f94c8 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Composing Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); - Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + Assert.AreEqual(21, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static ProfilingLogger GetTestProfilingLogger() From 1c1057412ecad3fd9472fc16b3cadd0cf4a1b07f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 12:40:40 +0200 Subject: [PATCH 151/278] move current rollback version to infinite editor --- .../content/umbcontentnodeinfo.directive.js | 10 ++ .../src/common/services/editor.service.js | 20 +++ .../rollback/rollback.controller.js | 129 ++++++++++++++++++ .../infiniteeditors/rollback/rollback.html | 92 +++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index d274cb495c..416774d6b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -103,6 +103,16 @@ scope.openRollback = function() { console.log("rollback"); + var rollback = { + node: scope.node, + submit: function(model) { + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.rollback(rollback); }; function loadAuditTrail() { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index eab167c2ec..7d1ef3e9b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -180,6 +180,25 @@ open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#rollback + * @methodOf umbraco.services.editorService + * + * @description + * Opens a rollback editor in infinite editing. + * @param {String} editor.node The node to rollback + * @param {Callback} editor.submit Saves, submits, and closes the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + + function rollback(editor) { + editor.view = "views/common/infiniteeditors/rollback/rollback.html"; + editor.size = "small"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#linkPicker @@ -481,6 +500,7 @@ copy: copy, move: move, embed: embed, + rollback: rollback, linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js new file mode 100644 index 0000000000..8ed6f68df6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -0,0 +1,129 @@ +(function () { + "use strict"; + + function RollbackController($scope, localizationService, assetsService) { + + var vm = this; + + vm.rollback = rollback; + vm.loadVersion = loadVersion; + vm.submit = submit; + vm.close = close; + + ////////// + + function onInit() { + + vm.loading = true; + vm.variantVersions = []; + vm.diff = null; + + // set default title + if(!$scope.model.title) { + localizationService.localize("actions_rollback").then(function(value){ + $scope.model.title = value; + }); + } + + // Load in diff library + assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { + + vm.currentVersion = { + "id": 1, + "createDate": "22/08/2018 13.32", + "name": "Forside", + "properties": [ + { + "alias": "headline", + "label": "Headline", + "value": "Velkommen" + }, + { + "alias": "text", + "label": "Text", + "value": "This is my danish Content" + } + ] + }; + + vm.previousVersions = [ + { + "id": 2, + "name": "Forside", + "createDate": "21/08/2018 19.25" + } + ]; + + vm.loading = false; + + }); + + } + + function rollback() { + console.log("rollback"); + } + + /** + * This will load in a new version + */ + function loadVersion(id) { + + // fake load version + + vm.diff = {}; + vm.diff.properties = []; + + var oldVersion = { + "id": 2, + "name": "Foride", + "properties": [ + { + "alias": "headline", + "label": "Headline", + "value": "" + }, + { + "alias": "text", + "label": "Text", + "value": "This is my danish Content Test" + } + ] + }; + + // find diff in name + vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); + + // find diff in properties + angular.forEach(vm.currentVersion.properties, + function(newProperty, index){ + var oldProperty = oldVersion.properties[index]; + var diffProperty = { + "alias": newProperty.alias, + "label": newProperty.label, + "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) + }; + vm.diff.properties.push(diffProperty); + }); + + } + + function submit(model) { + if($scope.model.submit) { + $scope.model.submit(model); + } + } + + function close() { + if($scope.model.close) { + $scope.model.close(); + } + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html new file mode 100644 index 0000000000..a09f91632a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -0,0 +1,92 @@ +
    + + + + + + + + + + + + + + +
    + +
    +

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

    + +
    + +
    + +
    + +
    Changes
    + + + + + + + + + + + + + +
    Name + + {{part.value}} + {{part.value}} + {{part.value}} + +
    {{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
    + +
    + +
    +
    +
    + + + + + + + + + + +
    + +
    \ No newline at end of file From c63c114563ea6d07a5529a219c62897ac502459e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 16 Oct 2018 12:58:21 +0100 Subject: [PATCH 152/278] Fixes the colors in the icon picker Checked that the color picker property editor is also OK/happy as well that re-uses the directive/component --- .../infiniteeditors/iconpicker/iconpicker.html | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index 8a7358116a..c0ca65de9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -1,6 +1,6 @@ -
    @@ -30,17 +30,18 @@ no-dirty-check />
    - +
    -
    - + - +
    • @@ -50,7 +51,7 @@
    - + From 203ea8019b6d40cb2beeb38553ab8e28414b01c9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 14:09:57 +0200 Subject: [PATCH 153/278] add language selector --- .../infiniteeditors/rollback/rollback.controller.js | 11 +++++++++++ .../common/infiniteeditors/rollback/rollback.html | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 8ed6f68df6..dc42568d54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -18,6 +18,17 @@ vm.variantVersions = []; vm.diff = null; + // preselect the active language + if($scope.model.node.variants.length > 1) { + var active = _.find($scope.model.node.variants, function (v) { + return v.active; + }); + + if(active) { + vm.selectedVariant = active; + } + } + // set default title if(!$scope.model.title) { localizationService.localize("actions_rollback").then(function(value){ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index a09f91632a..ef474551df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -18,6 +18,17 @@ + +
    +
    + +
    From 16282e10074a9761a288791d105b092dcae35c30 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 14:37:00 +0200 Subject: [PATCH 154/278] remove old client side files for rollback --- .../content/content.rollback.controller.js | 117 ------------------ .../src/views/content/rollback.html | 80 ------------ 2 files changed, 197 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js delete mode 100644 src/Umbraco.Web.UI.Client/src/views/content/rollback.html diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js deleted file mode 100644 index 9da43b1430..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js +++ /dev/null @@ -1,117 +0,0 @@ -(function () { - "use strict"; - - function ContentRollbackController($scope, assetsService) { - - var vm = this; - - vm.rollback = rollback; - vm.loadVersion = loadVersion; - vm.closeDialog = closeDialog; - - function onInit() { - - vm.loading = true; - vm.variantVersions = []; - vm.diff = null; - - // Load in diff library - assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; - - vm.loading = false; - - }); - - } - - function rollback() { - console.log("rollback"); - } - - /** - * This will load in a new version - */ - function loadVersion(id) { - - // fake load version - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.diff = {}; - vm.diff.properties = []; - - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); - - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; - var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) - }; - vm.diff.properties.push(diffProperty); - }); - - } - - /** - * This will close the dialog - */ - function closeDialog() { - $scope.nav.hideDialog(); - } - - onInit(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Content.RollbackController", ContentRollbackController); - -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html b/src/Umbraco.Web.UI.Client/src/views/content/rollback.html deleted file mode 100644 index 389b8f30db..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html +++ /dev/null @@ -1,80 +0,0 @@ -
    - -
    - - - -
    - -
    -
    Rollback {{ currentNode.name }}
    - -
    -

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

    - -
    - -
    - -
    - -
    Changes
    - - - - - - - - - - - - - -
    Name - - {{part.value}} - {{part.value}} - {{part.value}} - -
    {{property.label}} - - {{part.value}} - {{part.value}} - {{part.value}} - -
    - -
    - -
    -
    - - - -
    \ No newline at end of file From 6346710b97200130c80536cadf615fd9e019e902 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 16 Oct 2018 15:05:17 +0200 Subject: [PATCH 155/278] Fix tests --- .../ContentApps/ContentAppDefinitionCollection.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs index bdd7455386..559c3e3fb6 100644 --- a/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs +++ b/src/Umbraco.Web/ContentApps/ContentAppDefinitionCollection.cs @@ -19,10 +19,19 @@ namespace Umbraco.Web.ContentApps _logger = logger; } + private IEnumerable GetCurrentUserGroups() + { + var umbracoContext = Composing.Current.UmbracoContext; + var currentUser = umbracoContext?.Security?.CurrentUser; + return currentUser == null + ? Enumerable.Empty() + : currentUser.Groups; + + } + public IEnumerable GetContentAppsFor(object o, IEnumerable userGroups=null) { - var currentUser = UmbracoContext.Current.Security.CurrentUser; - var roles = currentUser.Groups; + var roles = GetCurrentUserGroups(); var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); var aliases = new HashSet(); From fd6f9fc83b2a25cdd6d76d45ca7fd52724f32687 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 16 Oct 2018 15:11:47 +0200 Subject: [PATCH 156/278] remove rollback context menu item --- src/Umbraco.Web/Trees/ContentTreeController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 3a990a7741..436c65d95d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -238,7 +238,6 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, true); - AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); From 69640515cff0a4d70f36b087ed8544af2351248a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Oct 2018 13:44:24 +1100 Subject: [PATCH 157/278] fixes issue with sorting on published --- .../Repositories/Implement/DocumentRepository.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 016e7d4f02..4ab4b556b4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -141,7 +141,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .LeftJoin(nested => nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); + .On((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); @@ -882,10 +882,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // variant: left join may yield NULL or something, and that determines published var joins = Sql() - .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype") - .LeftJoin(nested => - nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == ordering.Culture, "ccv", "lang"), "ccv") - .On((pcv, ccv) => pcv.Id == ccv.VersionId, "pcv", "ccv"); // join on *published* content version + .InnerJoin("ctype").On((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype"); sql = InsertJoins(sql, joins); From 5c796b15ade151e018e93e20b7cc2c1914099e0e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 11:01:33 +0200 Subject: [PATCH 158/278] Remove console.log --- .../components/content/umbcontentnodeinfo.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 416774d6b4..327d99a8c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -102,7 +102,7 @@ }; scope.openRollback = function() { - console.log("rollback"); + var rollback = { node: scope.node, submit: function(model) { From c05495f3587d29f41c33728284cf58d8708560d5 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 12:15:57 +0100 Subject: [PATCH 159/278] WIP: Adds a new API endpoint in the editor/webapi for ContenController to list rollback versions available for a culture --- src/Umbraco.Web/Editors/ContentController.cs | 56 +++++++++++++++++++ .../Models/ContentEditing/RollbackVersion.cs | 13 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 70 insertions(+) create mode 100644 src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 1fcee6d727..d35a801766 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1702,5 +1702,61 @@ namespace Umbraco.Web.Editors Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions); } + [HttpGet] + public IEnumerable GetRollbackVersions(int contentId, string cultureName) + { + var rollbackVersions = new List(); + + //Return a list of all versions of a specific content node + var versions = Services.ContentService.GetVersions(contentId); + + //Not all nodes are variants & thus culture can be null + //Only filter the collection + if(cultureName != null) + { + versions = versions.Where(x => x.PublishDate == x.GetPublishDate(cultureName)); + } + + foreach(var version in versions) + { + var rollbackVersion = new RollbackVersion(); + + //Version ID + rollbackVersion.VersionId = version.VersionId; + + //Date of version + var cultureDate = version.GetPublishDate(cultureName); + if (cultureDate.HasValue) + { + rollbackVersion.VersionDate = cultureDate.Value; + } + else if (version.PublishDate.HasValue) + { + rollbackVersion.VersionDate = version.PublishDate.Value; + } + + //Name of publisher + //TODO: Reviewer would this extra info be expensive? + var publisherId = version.PublisherId; + + if (publisherId.HasValue) + { + var publisher = Services.UserService.GetUserById(version.PublisherId.Value); + rollbackVersion.VersionAuthorName = publisher.Name; + } + + rollbackVersions.Add(rollbackVersion); + } + + return rollbackVersions; + } + + //[EnsureUserPermissionForContent("id", 'D')] + [HttpPost] + public void PostRollback() + { + + } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs new file mode 100644 index 0000000000..d496a89d35 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Web.Models.ContentEditing +{ + public class RollbackVersion + { + public int VersionId { get; set; } + + public DateTime VersionDate { get; set; } + + public string VersionAuthorName { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index faf08849ce..5235728814 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -147,6 +147,7 @@ + From 7c1c6f68063fa42c95f8949a77f1f456ac963d73 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:57:35 +0200 Subject: [PATCH 160/278] Fix AlterTableBuilder, was ignoring primary keys --- .../Expressions/AlterColumnExpression.cs | 1 - .../Alter/Table/AlterTableBuilder.cs | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs index f19fc94c97..1b00b03ca2 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Expressions/AlterColumnExpression.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Expressions protected override string GetSql() { - return string.Format(SqlSyntax.AlterColumn, SqlSyntax.GetQuotedTableName(TableName), SqlSyntax.Format(Column)); diff --git a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs index 989ce95002..8d95d9c14c 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs @@ -2,6 +2,8 @@ using NPoco; using Umbraco.Core.Migrations.Expressions.Alter.Expressions; using Umbraco.Core.Migrations.Expressions.Common.Expressions; +using Umbraco.Core.Migrations.Expressions.Create.Expressions; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Migrations.Expressions.Alter.Table @@ -87,6 +89,21 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table public IAlterTableColumnOptionBuilder PrimaryKey() { CurrentColumn.IsPrimaryKey = true; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } @@ -94,6 +111,22 @@ namespace Umbraco.Core.Migrations.Expressions.Alter.Table { CurrentColumn.IsPrimaryKey = true; CurrentColumn.PrimaryKeyName = primaryKeyName; + + // see notes in CreateTableBuilder + if (Expression.DatabaseType.IsMySql() == false) + { + var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey) + { + Constraint = + { + ConstraintName = primaryKeyName, + TableName = Expression.TableName, + Columns = new[] { CurrentColumn.Name } + } + }; + Expression.Expressions.Add(expression); + } + return this; } From d9864eb6d6beda445b34c50a9927bab0a5560674 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 13:19:09 +0100 Subject: [PATCH 161/278] * Make the culturename nullable for invariant content * fallback to update date and writer id if its not published --- src/Umbraco.Web/Editors/ContentController.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d35a801766..0fe017925b 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1703,7 +1703,7 @@ namespace Umbraco.Web.Editors } [HttpGet] - public IEnumerable GetRollbackVersions(int contentId, string cultureName) + public IEnumerable GetRollbackVersions(int contentId, string cultureName = null) { var rollbackVersions = new List(); @@ -1730,20 +1730,17 @@ namespace Umbraco.Web.Editors { rollbackVersion.VersionDate = cultureDate.Value; } - else if (version.PublishDate.HasValue) + else { - rollbackVersion.VersionDate = version.PublishDate.Value; + rollbackVersion.VersionDate = version.UpdateDate; } //Name of publisher //TODO: Reviewer would this extra info be expensive? var publisherId = version.PublisherId; - - if (publisherId.HasValue) - { - var publisher = Services.UserService.GetUserById(version.PublisherId.Value); - rollbackVersion.VersionAuthorName = publisher.Name; - } + var userId = version.PublisherId.HasValue ? version.PublisherId.Value : version.WriterId; + var publisher = Services.UserService.GetUserById(userId); + rollbackVersion.VersionAuthorName = publisher.Name; rollbackVersions.Add(rollbackVersion); } From 27ce1f34bb43020526b5a07a75967ebd509610ee Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 13:24:23 +0100 Subject: [PATCH 162/278] Make the JSON model over the API control be the correct casing & make Mads happy :) --- src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs index d496a89d35..88df12883d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -1,13 +1,18 @@ using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { + [DataContract(Name = "rollbackVersion", Namespace = "")] public class RollbackVersion { + [DataMember(Name = "versionId")] public int VersionId { get; set; } + [DataMember(Name = "versionDate")] public DateTime VersionDate { get; set; } + [DataMember(Name = "versionAuthorName")] public string VersionAuthorName { get; set; } } } From 2be227358961e53584441d8d3f0303ca8f44938b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 14:33:42 +0200 Subject: [PATCH 163/278] wire up versions end point --- .../src/common/resources/content.resource.js | 32 +++++++++++++++++++ .../rollback/rollback.controller.js | 17 +++++----- .../infiniteeditors/rollback/rollback.html | 2 +- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..8de368be1a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -808,6 +808,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to create blueprint from content with id " + contentId ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns an array of previous version id's, given a node id and a culture + * + * ##usage + *
    +          * contentResource.getRollbackVersions(id, culture)
    +          *    .then(function(versions) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the url. + * + */ + getRollbackVersions: function (contentId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { + contentId: contentId, cultureName: culture + }) + ), + "Failed to get rollback versions for content item with id " + contentId + ); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index dc42568d54..ec5b3f2242 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function RollbackController($scope, localizationService, assetsService) { + function RollbackController($scope, contentResource, localizationService, assetsService) { var vm = this; @@ -57,13 +57,14 @@ ] }; - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; + const nodeId = $scope.model.node.id; + const culture = vm.selectedVariant ? vm.selectedVariant.language.culture : null; + + contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + console.log(data); + vm.previousVersions = data; + }); vm.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index ef474551df..a4f571f832 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -39,7 +39,7 @@ From d324466fe1c57166f2e1310de109df095a9d8ff7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:58:18 +0200 Subject: [PATCH 164/278] Fix LockDto, had wrong primary key + missing rows --- src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs | 1 + src/Umbraco.Core/Persistence/Dtos/LockDto.cs | 4 ++-- src/Umbraco.Tests/Persistence/LocksTests.cs | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index ba491fb5e1..cf00e79800 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -149,6 +149,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); } diff --git a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs index 833d262e26..b5878141f3 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LockDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LockDto.cs @@ -4,12 +4,12 @@ using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos { [TableName(Constants.DatabaseSchema.Tables.Lock)] - [PrimaryKey("id")] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class LockDto { [Column("id")] - [PrimaryKeyColumn(Name = "PK_umbracoLock")] + [PrimaryKeyColumn(Name = "PK_umbracoLock", AutoIncrement = false)] public int Id { get; set; } [Column("value")] diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index 4dfb90c8fd..819dbc89ed 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -25,12 +25,9 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { var database = scope.Database; - database.Execute("SET IDENTITY_INSERT umbracoLock ON"); database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" }); database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" }); - database.Execute("SET IDENTITY_INSERT umbracoLock OFF"); - database.CompleteTransaction(); scope.Complete(); } } From 71cec84f78818cd25003bd5533e7bb1bdf53e977 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 13:58:42 +0200 Subject: [PATCH 165/278] Fix MigrationExpressionBase, minor typo --- src/Umbraco.Core/Migrations/MigrationExpressionBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs index f1c535b466..4c5a1f1aa7 100644 --- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Migrations if (string.IsNullOrWhiteSpace(sql)) { - Logger.Info(GetType(), "SQL [{ContextIndex}: ", Context.Index); + Logger.Info(GetType(), "SQL [{ContextIndex}]: ", Context.Index); } else { From e80ea9830cc5ec0668cc357d2f8010551ed1d837 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 15:09:00 +0200 Subject: [PATCH 166/278] Fix bad LockDto primary key w/migration --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs | 23 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 25 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index eeaf7533a9..5b0838573e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -137,6 +137,7 @@ namespace Umbraco.Core.Migrations.Upgrade // resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}... Chain("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); + Chain("{77874C77-93E5-4488-A404-A630907CEEF0}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs new file mode 100644 index 0000000000..fbb233927b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs @@ -0,0 +1,23 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class FixLockTablePrimaryKey : MigrationBase + { + public FixLockTablePrimaryKey(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // at some point, the KeyValueService dropped the PK and failed to re-create it, + // so the PK is gone - make sure we have one, and create if needed + + var constraints = SqlSyntax.GetConstraintsPerTable(Database); + var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock"); + + if (!exists) + Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do(); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index aef18e59db..6951a9e17a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -357,6 +357,7 @@ + From 581658086f7967da0d5768826b87d2ce4403e066 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 15:09:59 +0200 Subject: [PATCH 167/278] Fix KeyValueService for proper initialization --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 31 +++----- .../Services/Implement/KeyValueService.cs | 79 ++++++++++++++----- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 8f3b8991e4..0d967cb054 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; +using Umbraco.Core.Services.Implement; namespace Umbraco.Core.Runtime { @@ -313,21 +314,19 @@ namespace Umbraco.Core.Runtime // else // look for a matching migration entry - bypassing services entirely - they are not 'up' yet // fixme - in a LB scenario, ensure that the DB gets upgraded only once! - bool exists; + bool noUpgrade; try { - exists = EnsureUmbracoUpgradeState(databaseFactory, logger); + noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); } catch (Exception e) { - // can connect to the database but cannot access the migration table... need to install + // can connect to the database but cannot check the upgrade state... oops logger.Warn(e, "Could not check the upgrade state."); - logger.Debug("Could not check the upgrade state, need to install Umbraco."); - _state.Level = RuntimeLevel.Install; - return; + throw new BootFailedException("Could not check the upgrade state.", e); } - if (exists) + if (noUpgrade) { // the database version matches the code & files version, all clear, can run _state.Level = RuntimeLevel.Run; @@ -345,27 +344,19 @@ namespace Umbraco.Core.Runtime protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) { - // no scope, no key value service - just directly accessing the database - var umbracoPlan = new UmbracoPlan(); var stateValueKey = Upgrader.GetStateValueKey(umbracoPlan); - string state; + // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - var sql = databaseFactory.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == stateValueKey); - state = database.FirstOrDefault(sql)?.Value; + _state.CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + _state.FinalMigrationState = umbracoPlan.FinalState; } - _state.CurrentMigrationState = state; - _state.FinalMigrationState = umbracoPlan.FinalState; + logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, _state.CurrentMigrationState ?? ""); - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", _state.FinalMigrationState, state ?? ""); - - return state == _state.FinalMigrationState; + return _state.CurrentMigrationState == _state.FinalMigrationState; } #region Locals diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index cb1c423535..e0d78b0258 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations; -using Umbraco.Core.Migrations.Expressions.Create; using Umbraco.Core.Scoping; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -35,23 +34,23 @@ namespace Umbraco.Core.Services.Implement private void Initialize() { - // all this cannot be achieved via an UmbracoPlan migration since it needs to - // run before any migration, in order to figure out the current plan's state. - // (does not prevent us from using a migration to do it, though) + // the key/value service is entirely self-managed, because it is used by the + // upgrader and anything we might change need to happen before everything else + + // if already running 8, either following an upgrade or an install, + // then everything should be ok (the table should exist, etc) + + if (UmbracoVersion.Local.Major >= 8) + return; + + // else we are upgrading from 7, we can assume that the locks table + // exists, but we need to create everything for key/value using (var scope = _scopeProvider.CreateScope()) { - // assume that if the lock object for key/value exists, then everything is ok - if (scope.Database.Exists(Constants.Locks.KeyValues)) - { - scope.Complete(); - return; - } - var context = new MigrationContext(scope.Database, _logger); var initMigration = new InitializeMigration(context); initMigration.Migrate(); - scope.Complete(); } } @@ -59,7 +58,7 @@ namespace Umbraco.Core.Services.Implement /// /// A custom migration that executes standalone during the Initialize phase of this service. /// - private class InitializeMigration : MigrationBase + internal class InitializeMigration : MigrationBase { public InitializeMigration(IMigrationContext context) : base(context) @@ -67,26 +66,47 @@ namespace Umbraco.Core.Services.Implement public override void Migrate() { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + // create a temp. id column and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set nid = id").Do(); + // drop the id column entirely (cannot just drop identity) Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // recreate the id column without identity and copy values Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); Execute.Sql("update umbracoLock set id = nid").Do(); + // drop the temp. id column Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + // complete the primary key Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - - // insert the key-value lock - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); - - // create the key-value table if it's not there - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue) == false) - Create.Table().Do(); } } @@ -169,5 +189,22 @@ namespace Umbraco.Core.Services.Implement return true; } + + /// + /// Gets a value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + internal static string GetValue(IUmbracoDatabase database, string key) + { + // not 8 yet = no key/value table, no value + if (UmbracoVersion.Local.Major < 8) + return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } } } From 214d6a9834bed546194101f1490691f082ffe7ba Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 15:17:43 +0200 Subject: [PATCH 168/278] show correct title and date for current version + update versions when language is changed --- .../rollback/rollback.controller.js | 66 ++++++++++--------- .../infiniteeditors/rollback/rollback.html | 6 +- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index ec5b3f2242..cbbc4dd182 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -7,6 +7,8 @@ vm.rollback = rollback; vm.loadVersion = loadVersion; + vm.changeLanguage = changeLanguage; + vm.changeVersion = changeVersion; vm.submit = submit; vm.close = close; @@ -17,15 +19,23 @@ vm.loading = true; vm.variantVersions = []; vm.diff = null; + vm.currentVersion = null; - // preselect the active language + // find the current version for invariant nodes + if($scope.model.node.variants.length === 1) { + vm.currentVersion = $scope.model.node.variants[0]; + } + + // find the current version for nodes with variants if($scope.model.node.variants.length > 1) { var active = _.find($scope.model.node.variants, function (v) { return v.active; }); + // preselect the language in the dropdown if(active) { - vm.selectedVariant = active; + vm.selectedLanguage = active; + vm.currentVersion = active; } } @@ -39,39 +49,35 @@ // Load in diff library assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - const nodeId = $scope.model.node.id; - const culture = vm.selectedVariant ? vm.selectedVariant.language.culture : null; - - contentResource.getRollbackVersions(nodeId, culture) - .then(function(data){ - console.log(data); - vm.previousVersions = data; - }); - - vm.loading = false; + getVersions().then(function(){ + vm.loading = false; + }); }); } + function changeLanguage(language) { + vm.currentVersion = language; + getVersions(); + } + + function changeVersion(version) { + console.log("version", version); + } + + function getVersions() { + + const nodeId = $scope.model.node.id; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + console.log("new", data); + vm.previousVersions = data; + }); + } + function rollback() { console.log("rollback"); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index a4f571f832..6a17cac09b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -23,9 +23,9 @@
    @@ -40,7 +40,7 @@ class="input-block-level" ng-model="vm.selectedVersion" ng-options="version.versionDate for version in vm.previousVersions track by version.versionId" - ng-change="vm.loadVersion(vm.selectedVersion.id)"> + ng-change="vm.changeVersion(vm.selectedVersion.id)">
    From 2baafd9c9195a05336d47f4b3b91e4a87157a8ef Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 14:39:11 +0100 Subject: [PATCH 169/278] * Renamed API variables to 'culture' to make Stephan happy - updated the AngularJS HTTP Resource call too * Stubbed out some other WIP code - for getting a specific version & attempting to perform a rollback --- .../src/common/resources/content.resource.js | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 48 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 8de368be1a..6c0cb5e6b2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -835,7 +835,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { - contentId: contentId, cultureName: culture + contentId: contentId, culture: culture }) ), "Failed to get rollback versions for content item with id " + contentId diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 0fe017925b..85b42e59c3 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1703,7 +1703,7 @@ namespace Umbraco.Web.Editors } [HttpGet] - public IEnumerable GetRollbackVersions(int contentId, string cultureName = null) + public IEnumerable GetRollbackVersions(int contentId, string culture = null) { var rollbackVersions = new List(); @@ -1712,10 +1712,10 @@ namespace Umbraco.Web.Editors //Not all nodes are variants & thus culture can be null //Only filter the collection - if(cultureName != null) - { - versions = versions.Where(x => x.PublishDate == x.GetPublishDate(cultureName)); - } + //if(cultureName != null) + //{ + // versions = versions.Where(x => x.PublishDate == x.GetPublishDate(culture)); + //} foreach(var version in versions) { @@ -1725,7 +1725,7 @@ namespace Umbraco.Web.Editors rollbackVersion.VersionId = version.VersionId; //Date of version - var cultureDate = version.GetPublishDate(cultureName); + var cultureDate = version.GetPublishDate(culture); if (cultureDate.HasValue) { rollbackVersion.VersionDate = cultureDate.Value; @@ -1748,12 +1748,40 @@ namespace Umbraco.Web.Editors return rollbackVersions; } - //[EnsureUserPermissionForContent("id", 'D')] - [HttpPost] - public void PostRollback() + [HttpGet] + public ContentItemDisplay GetRollbackVersion(int versionId) { - + var version = Services.ContentService.GetVersion(versionId); + var content = MapToDisplay(version); + return content; } + [HttpPost] + public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") + { + //TODO: Do we log something - so there is a trail in the logs + //Of who performed the rollback of what document, time + + //Get the current copy of the node + var content = Services.ContentService.GetById(contentId); + + //Get the version + var version = Services.ContentService.GetVersion(versionId); + + //Copy the changes from the version + content.CopyFrom(version); + + //Save & Publish the update + var publishResult = Services.ContentService.SaveAndPublish(content, culture, Security.GetUserId().ResultOr(0)); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + AddMessageForPublishStatus(publishResult, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + } } } From eb8b2698460eaf9766a2aa438093a90c5e725aae Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 16:02:47 +0200 Subject: [PATCH 170/278] wire up getRollbackVersion endpoint --- .../src/common/resources/content.resource.js | 33 +++++++++++++++++++ .../rollback/rollback.controller.js | 9 +++-- .../infiniteeditors/rollback/rollback.html | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 6c0cb5e6b2..34a61cca91 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -840,9 +840,42 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to get rollback versions for content item with id " + contentId ); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersion + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a previous version of a content item + * + * ##usage + *
    +          * contentResource.getRollbackVersion(versionId)
    +          *    .then(function(version) {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} versionId The version Id + * @returns {Promise} resourcePromise object containing the url. + * + */ + getRollbackVersion: function (versionId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { + versionId: versionId + }) + ), + "Failed to get version for content item with id " + versionId + ); } + }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index cbbc4dd182..24fab8f30a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -6,7 +6,6 @@ var vm = this; vm.rollback = rollback; - vm.loadVersion = loadVersion; vm.changeLanguage = changeLanguage; vm.changeVersion = changeVersion; vm.submit = submit; @@ -64,6 +63,12 @@ function changeVersion(version) { console.log("version", version); + contentResource.getRollbackVersion(version.versionId) + .then(function(data){ + console.log(data); + //createDiff(vm.currentVersion, data); + }); + } function getVersions() { @@ -85,7 +90,7 @@ /** * This will load in a new version */ - function loadVersion(id) { + function createDiff(currentVersion, previousVersion) { // fake load version diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 6a17cac09b..70dd189328 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -40,7 +40,7 @@ class="input-block-level" ng-model="vm.selectedVersion" ng-options="version.versionDate for version in vm.previousVersions track by version.versionId" - ng-change="vm.changeVersion(vm.selectedVersion.id)"> + ng-change="vm.changeVersion(vm.selectedVersion)">
    From fff7891e533d76d2d1835013e96c46f869cbadd4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 17 Oct 2018 15:09:29 +0100 Subject: [PATCH 171/278] Filter rollback version to display the VariantDisplay model instead --- src/Umbraco.Web/Editors/ContentController.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 85b42e59c3..73ad3ae81a 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1749,11 +1749,20 @@ namespace Umbraco.Web.Editors } [HttpGet] - public ContentItemDisplay GetRollbackVersion(int versionId) + public ContentVariantDisplay GetRollbackVersion(int versionId, string culture = null) { var version = Services.ContentService.GetVersion(versionId); var content = MapToDisplay(version); - return content; + + + //No culture set - so this is an invariant node - so just list me the first item in here + //TODO: Tripple check invariant nodes still has one item in the collection but with a language of null + if (culture == null) + { + return content.Variants.FirstOrDefault(); + } + + return content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); } [HttpPost] From 15968bc99b26bafd91aeb762311e23982e472013 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 17 Oct 2018 17:27:39 +0200 Subject: [PATCH 172/278] Fix dates in document versions --- src/Umbraco.Core/Models/Content.cs | 28 ++- .../Implement/DocumentRepository.cs | 8 + .../Services/ContentServiceTests.cs | 190 +++++++++++++++++- 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 238d87b186..5d8a4f7222 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -227,7 +227,27 @@ namespace Umbraco.Core.Models public bool WasCulturePublished(string culture) // just check _publishInfosOrig - a copy of _publishInfos // a non-available culture could not become published anyways - => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); + + // adjust dates to sync between version, cultures etc + // used by the repo when persisting + internal void AdjustDates(DateTime date) + { + foreach (var culture in PublishedCultures.ToList()) + { + if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) + continue; + + if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig) + && publishInfosOrig.Date == publishInfos.Date) + continue; + + _publishInfos[culture] = (publishInfos.Name, date); + + if (CultureNames.TryGetValue(culture, out var name)) + SetCultureInfo(culture, name, date); + } + } /// public bool IsCultureEdited(string culture) @@ -343,6 +363,12 @@ namespace Umbraco.Core.Models SetPublishInfo(c, name, DateTime.Now); } } + else if (culture == null) // invariant culture + { + if (string.IsNullOrWhiteSpace(Name)) + return false; + // PublishName set by repository - nothing to do here + } else // one single culture { var name = GetCultureName(culture); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index bf41cd1ad1..3ceacbfc96 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -341,6 +341,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // persist the variations if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) @@ -499,6 +503,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.ContentType.VariesByCulture()) { + // bump dates to align cultures to version + if (publishing) + content.AdjustDates(contentVersionDto.VersionDate); + // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) if (name != content.GetPublishName(culture)) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f187b8b70d..a82ccd2a0e 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2111,7 +2111,7 @@ namespace Umbraco.Tests.Services var rollback = contentService.GetById(NodeDto.NodeIdSeed + 4); var rollto = contentService.GetVersion(version1); rollback.CopyFrom(rollto); - rollback.Name = rollto.Name; // must do it explicitely + rollback.Name = rollto.Name; // must do it explicitly contentService.Save(rollback); Assert.IsNotNull(rollback); @@ -2161,6 +2161,194 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Jane Doe", content.GetValue("author")); } + [Test] + public void Can_Rollback_Version_On_Multilingual() + { + var langFr = new Language("fr"); + var langDa = new Language("da"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langDa); + + var contentType = MockedContentTypes.CreateSimpleContentType("multi", "Multi"); + contentType.Key = new Guid("45FF9A70-9C5F-448D-A476-DCD23566BBF8"); + contentType.Variations = ContentVariation.Culture; + var p1 = contentType.PropertyTypes.First(); + p1.Variations = ContentVariation.Culture; + ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType! + ServiceContext.ContentTypeService.Save(contentType); + + var page = new Content("Page", -1, contentType) + { + Level = 1, + SortOrder = 1, + CreatorId = 0, + WriterId = 0, + Key = new Guid("D7B84CC9-14AE-4D92-A042-023767AD3304") + }; + + page.SetCultureName("fr1", "fr"); + page.SetCultureName("da1", "da"); + ServiceContext.ContentService.Save(page); + var versionId0 = page.VersionId; + + page.SetValue(p1.Alias, "v1fr", "fr"); + page.SetValue(p1.Alias, "v1da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId1 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr2", "fr"); + page.SetValue(p1.Alias, "v2fr", "fr"); + ServiceContext.ContentService.SaveAndPublish(page, "fr"); + var versionId2 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("da2", "da"); + page.SetValue(p1.Alias, "v2da", "da"); + ServiceContext.ContentService.SaveAndPublish(page, "da"); + var versionId3 = page.VersionId; + + Thread.Sleep(250); + + page.SetCultureName("fr3", "fr"); + page.SetCultureName("da3", "da"); + page.SetValue(p1.Alias, "v3fr", "fr"); + page.SetValue(p1.Alias, "v3da", "da"); + ServiceContext.ContentService.SaveAndPublish(page); + var versionId4 = page.VersionId; + + // now get all versions + + var versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + Assert.AreEqual(5, versions.Length); + + // current version + Assert.AreEqual(versionId4, versions[0].VersionId); + Assert.AreEqual(versionId3, versions[0].PublishedVersionId); + // published version + Assert.AreEqual(versionId3, versions[1].VersionId); + Assert.AreEqual(versionId3, versions[1].PublishedVersionId); + // previous version + Assert.AreEqual(versionId2, versions[2].VersionId); + Assert.AreEqual(versionId3, versions[2].PublishedVersionId); + // previous version + Assert.AreEqual(versionId1, versions[3].VersionId); + Assert.AreEqual(versionId3, versions[3].PublishedVersionId); + // previous version + Assert.AreEqual(versionId0, versions[4].VersionId); + Assert.AreEqual(versionId3, versions[4].PublishedVersionId); + + Assert.AreEqual("fr3", versions[4].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[3].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[2].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[1].GetPublishName("fr")); + Assert.AreEqual("fr3", versions[0].GetPublishName("fr")); + + Assert.AreEqual("fr1", versions[4].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[3].GetCultureName("fr")); + Assert.AreEqual("fr2", versions[2].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[1].GetCultureName("fr")); + Assert.AreEqual("fr3", versions[0].GetCultureName("fr")); + + Assert.AreEqual("da3", versions[4].GetPublishName("da")); + Assert.AreEqual("da3", versions[3].GetPublishName("da")); + Assert.AreEqual("da3", versions[2].GetPublishName("da")); + Assert.AreEqual("da3", versions[1].GetPublishName("da")); + Assert.AreEqual("da3", versions[0].GetPublishName("da")); + + Assert.AreEqual("da1", versions[4].GetCultureName("da")); + Assert.AreEqual("da1", versions[3].GetCultureName("da")); + Assert.AreEqual("da2", versions[2].GetCultureName("da")); + Assert.AreEqual("da3", versions[1].GetCultureName("da")); + Assert.AreEqual("da3", versions[0].GetCultureName("da")); + + // all versions have the same publish infos + for (var i = 0; i < 5; i++) + { + Assert.AreEqual(versions[0].PublishDate, versions[i].PublishDate); + Assert.AreEqual(versions[0].GetPublishDate("fr"), versions[i].GetPublishDate("fr")); + Assert.AreEqual(versions[0].GetPublishDate("da"), versions[i].GetPublishDate("da")); + } + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // for all previous versions, UpdateDate is the published date + + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[4].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[3].GetUpdateDate("fr")); + Assert.AreEqual(versions[4].UpdateDate, versions[3].GetUpdateDate("da")); + + Assert.AreEqual(versions[3].UpdateDate, versions[2].GetUpdateDate("fr")); + Assert.AreEqual(versions[2].UpdateDate, versions[2].GetUpdateDate("da")); + + // for the published version, UpdateDate is the published date + + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("fr")); + Assert.AreEqual(versions[1].UpdateDate, versions[1].GetUpdateDate("da")); + Assert.AreEqual(versions[1].PublishDate, versions[1].UpdateDate); + + // for the current version, things are different + // UpdateDate is the date it was last saved + + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // so if we save again... + + page.SetCultureName("fr4", "fr"); + //page.SetCultureName("da4", "da"); + page.SetValue(p1.Alias, "v4fr", "fr"); + page.SetValue(p1.Alias, "v4da", "da"); + ServiceContext.ContentService.Save(page); + var versionId5 = page.VersionId; + + versions = ServiceContext.ContentService.GetVersions(page.Id).ToArray(); + + // we just update the current version + Assert.AreEqual(5, versions.Length); + Assert.AreEqual(versionId4, versionId5); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versions[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versions[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versions[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // alas, at the moment we do *not* properly track 'dirty' for cultures, meaning + // that we cannot synchronize dates the way we do with publish dates - and so this + // would fail - the version UpdateDate is greater than the cultures'. + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("fr")); + //Assert.AreEqual(versions[0].UpdateDate, versions[0].GetUpdateDate("da")); + + // now roll french back to its very first version + page.CopyFrom(versions[4], "fr"); // only the pure FR values + page.CopyFrom(versions[4], null); // so, must explicitly do the INVARIANT values too + page.SetCultureName(versions[4].GetPublishName("fr"), "fr"); + ServiceContext.ContentService.Save(page); + + // and voila, rolled back! + Assert.AreEqual(versions[4].GetPublishName("fr"), page.GetCultureName("fr")); + Assert.AreEqual(versions[4].GetValue(p1.Alias, "fr"), page.GetValue(p1.Alias, "fr")); + + // note that rolling back invariant values means we also rolled back... DA... at least partially + // bah? + } + [Test] public void Can_Save_Lazy_Content() { From ab34deb726cde40f94b59f866920190e2bbd7913 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 17 Oct 2018 20:37:15 +0200 Subject: [PATCH 173/278] add diff --- .../rollback/rollback.controller.js | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 24fab8f30a..784cb7621f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -65,8 +65,8 @@ console.log("version", version); contentResource.getRollbackVersion(version.versionId) .then(function(data){ - console.log(data); - //createDiff(vm.currentVersion, data); + const previousVersion = data; + createDiff(vm.currentVersion, previousVersion); }); } @@ -78,7 +78,6 @@ return contentResource.getRollbackVersions(nodeId, culture) .then(function(data){ - console.log("new", data); vm.previousVersions = data; }); } @@ -92,42 +91,24 @@ */ function createDiff(currentVersion, previousVersion) { - // fake load version - vm.diff = {}; vm.diff.properties = []; - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); + vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, previousVersion.name); - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; + // extract all properties from the tabs and create new object for the diff + vm.currentVersion.tabs.forEach((tab, tabIndex) => { + tab.properties.forEach((property, propertyIndex) => { + var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) + "alias": property.alias, + "label": property.label, + "diff": JsDiff.diffWords(property.value, oldProperty.value) }; vm.diff.properties.push(diffProperty); }); + }); } From bdf474f111dcb3b559e14d55cb51518aa5efdbfe Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 09:06:46 +0100 Subject: [PATCH 174/278] Typo in lazyload JS path for AuthUpgrade page --- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 01afc9f2ec..3c79d5458c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + From 3f82cd7677ebadbfa42d96d872dca3b5194316c7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 09:13:27 +0100 Subject: [PATCH 175/278] Update get rollback version to use a culture in the resource --- .../src/common/resources/content.resource.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 34a61cca91..bd64a2fdd8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -835,7 +835,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { - contentId: contentId, culture: culture + contentId: contentId, + culture: culture }) ), "Failed to get rollback versions for content item with id " + contentId @@ -853,21 +854,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * ##usage *
    -          * contentResource.getRollbackVersion(versionId)
    +          * contentResource.getRollbackVersion(versionId, culture)
               *    .then(function(version) {
               *        alert('its here!');
               *    });
               * 
    * * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant * @returns {Promise} resourcePromise object containing the url. * */ - getRollbackVersion: function (versionId) { + getRollbackVersion: function (versionId, culture) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { - versionId: versionId + versionId: versionId, + culture: culture }) ), "Failed to get version for content item with id " + versionId From 813f3f8d84760e13fccbb9581b666aa41928f4ba Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 10:21:13 +0200 Subject: [PATCH 176/278] a language should always be selected when a node has variants --- .../src/views/common/infiniteeditors/rollback/rollback.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 70dd189328..5fb618eb78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -26,7 +26,6 @@ ng-model="vm.selectedLanguage" ng-options="variant as variant.language.name for variant in model.node.variants track by variant.language.culture" ng-change="vm.changeLanguage(vm.selectedLanguage)"> -
    From fd0831618975583a712024fba25bf871920b4cfd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 11:10:27 +0200 Subject: [PATCH 177/278] wire up rollback button --- .../src/common/resources/content.resource.js | 40 ++++++++++++++++--- .../rollback/rollback.controller.js | 35 +++++++++++----- .../infiniteeditors/rollback/rollback.html | 3 +- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index bd64a2fdd8..71cbe3b8d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -828,7 +828,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @param {Int} id Id of node * @param {Int} culture if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing the url. + * @returns {Promise} resourcePromise object containing the versions * */ getRollbackVersions: function (contentId, culture) { @@ -843,7 +843,6 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ); }, - /** * @ngdoc method * @name umbraco.resources.contentResource#getRollbackVersion @@ -862,7 +861,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @param {Int} versionId The version Id * @param {Int} culture if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing the url. + * @returns {Promise} resourcePromise object containing the version * */ getRollbackVersion: function (versionId, culture) { @@ -875,10 +874,41 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to get version for content item with id " + versionId ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#rollback + * @methodOf umbraco.resources.contentResource + * + * @description + * Roll backs a content item to a previous version + * + * ##usage + *
    +          * contentResource.rollback(contentId, versionId, culture)
    +          *    .then(function() {
    +          *        alert('its here!');
    +          *    });
    +          * 
    + * + * @param {Int} id Id of node + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object + * + */ + rollback: function (contentId, versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostRollbackContent", { + contentId: contentId, versionId:versionId, culture:culture + }) + ), + "Failed to roll back content item with id " + contentId + ); } - - }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 784cb7621f..2aa628d5ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -65,8 +65,9 @@ console.log("version", version); contentResource.getRollbackVersion(version.versionId) .then(function(data){ - const previousVersion = data; - createDiff(vm.currentVersion, previousVersion); + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); }); } @@ -82,10 +83,6 @@ }); } - function rollback() { - console.log("rollback"); - } - /** * This will load in a new version */ @@ -95,10 +92,10 @@ vm.diff.properties = []; // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, previousVersion.name); + vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name); // extract all properties from the tabs and create new object for the diff - vm.currentVersion.tabs.forEach((tab, tabIndex) => { + currentVersion.tabs.forEach((tab, tabIndex) => { tab.properties.forEach((property, propertyIndex) => { var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; var diffProperty = { @@ -112,9 +109,27 @@ } - function submit(model) { + function rollback() { + + vm.rollbackButtonState = "busy"; + + const nodeId = $scope.model.node.id; + const versionId = vm.previousVersion.versionId; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.rollback(nodeId, versionId, culture) + .then(data => { + vm.rollbackButtonState = "success"; + submit(); + }, error => { + vm.rollbackButtonState = "error"; + }); + + } + + function submit() { if($scope.model.submit) { - $scope.model.submit(model); + $scope.model.submit($scope.model.submit); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 5fb618eb78..8c6c1f6de5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -91,8 +91,9 @@ + action="vm.rollback()"> From 15f5e9a98e801395124e949a564989aa4baa8e8f Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 18 Oct 2018 11:25:05 +0200 Subject: [PATCH 178/278] Typo in lazyload JS path for AuthUpgrade page --- src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml index 01afc9f2ec..3c79d5458c 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/AuthorizeUpgrade.cshtml @@ -72,7 +72,7 @@ @*And finally we can load in our angular app*@ - + From b2138d6376d3582baa175f819eefafc7bf7b43d8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 14:06:21 +0200 Subject: [PATCH 179/278] reload content after rollback --- .../directives/components/content/edit.controller.js | 11 +++++++++++ .../content/umbcontentnodeinfo.directive.js | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 98e02f3d55..6670a41ac4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -103,6 +103,17 @@ loadContent(); } })); + + evts.push(eventsService.on("editors.content.reload", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if(args && args.node && args.node.key === $scope.content.key) { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } + })); + } /** diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 327d99a8c6..e2292c50d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -106,6 +106,8 @@ var rollback = { node: scope.node, submit: function(model) { + const args = { node: scope.node }; + eventsService.emit("editors.content.reload", args); editorService.close(); }, close: function() { From a40a6e8f35185edfd1cb3bd422a090187969b12c Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 13:13:11 +0100 Subject: [PATCH 180/278] WIP: Adds logging & audit message Fixes the filtering & dates to display in the version list --- src/Umbraco.Web/Editors/ContentController.cs | 77 ++++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ad2734e8cf..b37bf82797 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1708,16 +1708,17 @@ namespace Umbraco.Web.Editors var rollbackVersions = new List(); //Return a list of all versions of a specific content node - var versions = Services.ContentService.GetVersions(contentId); - + //First item is our current item/state (cant rollback to ourselves) + var versions = Services.ContentService.GetVersions(contentId).Skip(1); + //Not all nodes are variants & thus culture can be null //Only filter the collection - //if(cultureName != null) - //{ - // versions = versions.Where(x => x.PublishDate == x.GetPublishDate(culture)); - //} + if (culture != null) + { + versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); + } - foreach(var version in versions) + foreach (var version in versions) { var rollbackVersion = new RollbackVersion(); @@ -1725,22 +1726,12 @@ namespace Umbraco.Web.Editors rollbackVersion.VersionId = version.VersionId; //Date of version - var cultureDate = version.GetPublishDate(culture); - if (cultureDate.HasValue) - { - rollbackVersion.VersionDate = cultureDate.Value; - } - else - { - rollbackVersion.VersionDate = version.UpdateDate; - } - - //Name of publisher - //TODO: Reviewer would this extra info be expensive? - var publisherId = version.PublisherId; - var userId = version.PublisherId.HasValue ? version.PublisherId.Value : version.WriterId; - var publisher = Services.UserService.GetUserById(userId); - rollbackVersion.VersionAuthorName = publisher.Name; + rollbackVersion.VersionDate = version.UpdateDate; + + //Name of writer/publisher/user + var writerId = version.WriterId; + var user = Services.UserService.GetUserById(writerId); + rollbackVersion.VersionAuthorName = user.Name; rollbackVersions.Add(rollbackVersion); } @@ -1768,8 +1759,10 @@ namespace Umbraco.Web.Editors [HttpPost] public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") { - //TODO: Do we log something - so there is a trail in the logs - //Of who performed the rollback of what document, time + var userId = Security.GetUserId().ResultOr(0); + + Logger.Info("User ID {UserId} is attempting to rollback content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); + Services.AuditService.Add(AuditType.RollBack, "YO YO YO IM ROLLING BACK", userId, contentId); //Get the current copy of the node var content = Services.ContentService.GetById(contentId); @@ -1778,16 +1771,38 @@ namespace Umbraco.Web.Editors var version = Services.ContentService.GetVersion(versionId); //Copy the changes from the version - content.CopyFrom(version); - - //Save & Publish the update - var publishResult = Services.ContentService.SaveAndPublish(content, culture, Security.GetUserId().ResultOr(0)); - if (publishResult.Success == false) + content.CopyFrom(version, culture); + + //Save the update + var saveResult = Services.ContentService.Save(content, userId); + if(saveResult.Success == false) { + Logger.Error("Unable to rollback content '{ContentId}' to version '{VersionId}'", contentId, versionId); + var notificationModel = new SimpleNotificationModel(); - AddMessageForPublishStatus(publishResult, notificationModel); + + switch (saveResult.Result) + { + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; + } + return Request.CreateValidationErrorResponse(notificationModel); } + + Logger.Info("User ID {UserId} rolled back content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); //return ok return Request.CreateResponse(HttpStatusCode.OK); From 510666cd7e7b35ae7852fd3d033eda3443e87353 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 18 Oct 2018 14:16:50 +0200 Subject: [PATCH 181/278] ContentService.GetPagedChildren is broken? --- .../Services/ContentServiceTests.cs | 102 ++++++++++++++++++ .../TestHelpers/TestWithDatabaseBase.cs | 2 + 2 files changed, 104 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index a82ccd2a0e..ef34f4bc24 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -25,6 +25,7 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; using System.Reflection; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Services { @@ -2744,6 +2745,107 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Paged_Children_WithFilterAndOrder() + { + var languageService = ServiceContext.LocalizationService; + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + var langDa = new Language("da-DK"); + + languageService.Save(langFr); + languageService.Save(langUk); + languageService.Save(langDa); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = contentTypeService.Get("umbTextpage"); + contentType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var o = new[] { 2, 1, 3, 0, 4 }; // randomly different + for (var i = 0; i < 5; i++) + { + var contentA = new Content(null, -1, contentType); + contentA.SetCultureName("contentA" + i + "uk", langUk.IsoCode); + contentA.SetCultureName("contentA" + o[i] + "fr", langFr.IsoCode); + contentA.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentA); + + var contentB = new Content(null, -1, contentType); + contentB.SetCultureName("contentB" + i + "uk", langUk.IsoCode); + contentB.SetCultureName("contentB" + o[i] + "fr", langFr.IsoCode); + contentB.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentB); + } + + // get all + var list = contentService.GetPagedChildren(-1, 0, 100, out var total).ToList(); + + Console.WriteLine("ALL"); + WriteList(list); + + // 10 items (there's already a Home content in there...) + Assert.AreEqual(11, total); + Assert.AreEqual(11, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Assert.AreEqual(0, total); + Assert.AreEqual(0, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langDa.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME da:'contentX'"); + WriteList(list); + + Assert.AreEqual(10, total); + Assert.AreEqual(10, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER ASC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + i + "fr", list[i].GetCultureName(langFr.IsoCode)); + + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", direction: Direction.Descending, culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER DESC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + (4-i) + "fr", list[i].GetCultureName(langFr.IsoCode)); + } + + private void WriteList(List list) + { + foreach (var content in list) + Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-UK"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); + Console.WriteLine("-"); + } + [Test] public void Can_SaveRead_Variations() { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 21f1ce82b2..6b52137542 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -64,6 +64,8 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; + protected ISqlContext SqlContext => Container.GetInstance(); + public override void SetUp() { base.SetUp(); From 2b83b44c9c48f7f8ec1a95502a46a24a77c0719c Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 18 Oct 2018 14:16:54 +0200 Subject: [PATCH 182/278] Fix ContentService.GetPagedChildren --- .../Persistence/NPocoSqlExtensions.cs | 11 ++-- .../Implement/ContentRepositoryBase.cs | 9 +-- .../Implement/DocumentRepository.cs | 55 ++++++++-------- .../Repositories/Implement/MediaRepository.cs | 9 ++- .../Implement/MemberRepository.cs | 12 ++-- .../Persistence/SqlContextExtensions.cs | 65 ++++++++++++++----- 6 files changed, 101 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 7f6eb72a25..a5ab62d25f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias); return sql.Where(s, a); } @@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2); return sql.Where(s, a); } @@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence /// Appends an ORDER BY DESC clause to the Sql statement. ///
    /// The Sql statement. - /// Expression specifying the fields. + /// Fields. /// The Sql statement. - public static Sql OrderByDescending(this Sql sql, params object[] fields) + public static Sql OrderByDescending(this Sql sql, params string[] fields) { return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC"))); } @@ -664,7 +664,7 @@ namespace Umbraco.Core.Persistence /// Adds columns to a SELECT Sql statement. ///
    /// The origin sql. - /// Expression indicating the column to select. + /// Columns to select. /// The Sql statement. public static Sql AndSelect(this Sql sql, params string[] fields) { @@ -688,7 +688,6 @@ namespace Umbraco.Core.Persistence return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields))); } - /// /// Adds columns to a SELECT Sql statement. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 41f0f16225..34bc3713f3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -252,8 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - var dbfield = GetQuotedFieldName("umbracoNode", "id"); - (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?! + var (dbfield, _) = SqlContext.VisitDto(x => x.NodeId); if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased? @@ -262,7 +261,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); - // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) if (!ordering.Culture.IsNullOrWhiteSpace()) @@ -353,6 +351,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); + // "variantName" alias is defined in DocumentRepository.GetBaseQuery + // fixme - what if it is NOT a document but a ... media or whatever? + // previously, we inserted the join+select *here* so we were sure to have it, + // but now that's not the case anymore! return "variantName"; } @@ -433,7 +435,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // sort and filter sql = PreparePageSql(sql, filter, ordering); - // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); totalRecords = Convert.ToInt32(pagedResult.TotalItems); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 3cb61ad8e1..df389c738a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetBaseQuery(queryType, true); } + // gets the COALESCE expression for variant/invariant name + private string VariantNameSqlExpression + => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); @@ -116,7 +120,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) - .AndSelect(SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName"); + + // select the variant name, coalesce to the invariant name, as "variantName" + .AndSelect(VariantNameSqlExpression + " AS variantName"); break; } @@ -135,18 +141,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .LeftJoin(nested => nested.InnerJoin("pdv") .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") - .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv") - //the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code - sql + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code .LeftJoin(nested => nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv"); + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); - // this would ensure we don't get the published version - keep for reference //sql // .WhereAny( @@ -157,7 +162,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (current) sql.Where(x => x.Current); // always get the current version - return sql; } @@ -246,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -420,7 +424,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; // check if we need to make any database changes at all if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) @@ -707,30 +711,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Sql filterSql = null; - // Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name - // if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead. - // The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same - // we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names. - var temp = Query().Where(x => x.Name.Contains("foo")); - var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; - + // if we have a filter, map its clauses to an Sql statement if (filter != null) { + // if the clause works on "name", we need to swap the field and use the variantName instead, + // so that querying also works on variant content (for instance when searching a listview). + + // figure out how the "name" field is going to look like - so we can look for it + var nameField = SqlContext.VisitModelField(x => x.Name); + filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) { - // fixme - is this the right way of doing it??? + var clauseSql = filterClause.Item1; + var clauseArgs = filterClause.Item2; - // - var where = filterClause.Item1.Split(' ')[0] == clause - // normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however - // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field - ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql - : filterClause.Item1; + // replace the name field + // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here + clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression); - filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", - filterClause.Item2); + // append the clause + filterSql.Append($"AND ({clauseSql})", clauseArgs); } } @@ -928,7 +929,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content)cached; + content[i] = (Content) cached; continue; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 2390ce9a7b..dbfdc8e980 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Many: sql = sql.Select(r => r.Select(x => x.NodeDto) - .Select(x => x.ContentVersionDto)); + .Select(x => x.ContentVersionDto)) + + // ContentRepositoryBase expects a variantName field to order by name + // for now, just return the plain invariant node name + // fixme media should support variants !! + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 84ef154ae8..fd79b231de 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,12 +6,12 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(x => x.ContentVersionDto) - .Select(x => x.ContentDto, r1 => - r1.Select(x => x.NodeDto))); + r.Select(x => x.ContentVersionDto) + .Select(x => x.ContentDto, r1 => + r1.Select(x => x.NodeDto))) + + // ContentRepositoryBase expects a variantName field to order by name + // so get it here, though for members it's just the plain node name + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs index e28816b6a4..249e2cafd0 100644 --- a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs @@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression. + /// + /// The type of the model. + /// An . + /// An expression to visit. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) VisitModel(this ISqlContext sqlContext, Expression> expression) + { + var visitor = new ModelToSqlExpressionVisitor(sqlContext.SqlSyntax, sqlContext.Mappers); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression representing a field. + /// + /// The type of the model. + /// An . + /// An expression to visit, representing a field. + /// The name of the field. + public static string VisitModelField(this ISqlContext sqlContext, Expression> field) + { + var (sql, _) = sqlContext.VisitModel(field); + + // going to return " = @0" + // take the first part only + var pos = sql.IndexOf(' '); + return sql.Substring(0, pos); } } -} \ No newline at end of file +} From a12d736b6bb61e2d837562d93b06dc1a8e8aec3a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 14:56:00 +0200 Subject: [PATCH 183/278] show user in version dropdown --- .../common/infiniteeditors/rollback/rollback.controller.js | 5 ++++- .../src/views/common/infiniteeditors/rollback/rollback.html | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 2aa628d5ef..f96c1f6a3d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -79,7 +79,10 @@ return contentResource.getRollbackVersions(nodeId, culture) .then(function(data){ - vm.previousVersions = data; + vm.previousVersions = data.map(version => { + version.displayValue = version.versionDate + " - " + version.versionAuthorName; + return version; + }); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 8c6c1f6de5..5faba3a3ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -38,7 +38,7 @@ From 09a744370140c36c5acbc578707100e504013550 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 15:16:37 +0200 Subject: [PATCH 184/278] dont show diff if a versionId is not set --- .../rollback/rollback.controller.js | 21 ++++++++++++------- .../content/umb-content-node-info.html | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index f96c1f6a3d..63672879b0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -62,14 +62,21 @@ } function changeVersion(version) { - console.log("version", version); - contentResource.getRollbackVersion(version.versionId) - .then(function(data){ - vm.previousVersion = data; - vm.previousVersion.versionId = version.versionId; - createDiff(vm.currentVersion, vm.previousVersion); - }); + if(version && version.versionId) { + + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + contentResource.getRollbackVersion(version.versionId, culture) + .then(function(data){ + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); + }); + + } else { + vm.diff = null; + } } function getVersions() { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 77a7161762..d8bc08491a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -49,7 +49,8 @@ button-style="outline" action="openRollback()" label-key="actions_rollback" - size="xs"> + size="xs" + add-ellipsis="true"> From 6f7d0c3a45f4025534731d805b2259460ac85393 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:24:32 +0100 Subject: [PATCH 185/278] Move logic out of controller into a Rollback method in the service level --- src/Umbraco.Core/Services/IContentService.cs | 13 +++ .../Services/Implement/ContentService.cs | 42 ++++++++++ src/Umbraco.Web/Editors/ContentController.cs | 83 +++++++------------ 3 files changed, 84 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 022bee8b41..3a0f920d48 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -461,5 +461,18 @@ namespace Umbraco.Core.Services IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); #endregion + + #region Rollback + + /// + /// Rolls back the content to a specific version. + /// + /// The id of the content node + /// The version ID to rollback to + /// An optional culture - specifying a culture will only rollback the culture content + /// The user ID who is performing the rollback + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a849813b13..d7f11a3e2f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2490,5 +2490,47 @@ namespace Umbraco.Core.Services.Implement } #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + //Get the current copy of the node + var content = GetById(id); + + //Get the version + var version = GetVersion(versionId); + + //Good ole null checks + if (content == null || version == null) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + //Copy the changes from the version + content.CopyFrom(version, culture); + + //Save the content for the rollback + var rollbackSaveResult = Save(content, userId); + + //Depending on the save result - is what we log & audit along with what we return + if(rollbackSaveResult.Success == false) + { + //Log the error/warning + Logger.Error("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + } + else + { + //Logging & Audit message + Logger.Error("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id); + } + + return rollbackSaveResult; + } + + #endregion } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b37bf82797..be4801ae61 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1707,9 +1707,8 @@ namespace Umbraco.Web.Editors { var rollbackVersions = new List(); - //Return a list of all versions of a specific content node - //First item is our current item/state (cant rollback to ourselves) - var versions = Services.ContentService.GetVersions(contentId).Skip(1); + //Return a list of all versions of a specific content node + var versions = Services.ContentService.GetVersions(contentId); //Not all nodes are variants & thus culture can be null //Only filter the collection @@ -1718,6 +1717,9 @@ namespace Umbraco.Web.Editors versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); } + //First item is our current item/state (cant rollback to ourselves) + versions = versions.Skip(1); + foreach (var version in versions) { var rollbackVersion = new RollbackVersion(); @@ -1744,68 +1746,41 @@ namespace Umbraco.Web.Editors { var version = Services.ContentService.GetVersion(versionId); var content = MapToDisplay(version); - - - //No culture set - so this is an invariant node - so just list me the first item in here - //TODO: Tripple check invariant nodes still has one item in the collection but with a language of null - if (culture == null) - { - return content.Variants.FirstOrDefault(); - } - - return content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + + return culture == null + ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); } [HttpPost] public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") { - var userId = Security.GetUserId().ResultOr(0); + var rollbackResult = Services.ContentService.Rollback(contentId, versionId, culture, Security.GetUserId().ResultOr(0)); - Logger.Info("User ID {UserId} is attempting to rollback content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); - Services.AuditService.Add(AuditType.RollBack, "YO YO YO IM ROLLING BACK", userId, contentId); + if (rollbackResult.Success) + return Request.CreateResponse(HttpStatusCode.OK); - //Get the current copy of the node - var content = Services.ContentService.GetById(contentId); + var notificationModel = new SimpleNotificationModel(); - //Get the version - var version = Services.ContentService.GetVersion(versionId); - - //Copy the changes from the version - content.CopyFrom(version, culture); - - //Save the update - var saveResult = Services.ContentService.Save(content, userId); - if(saveResult.Success == false) + switch (rollbackResult.Result) { - Logger.Error("Unable to rollback content '{ContentId}' to version '{VersionId}'", contentId, versionId); - - var notificationModel = new SimpleNotificationModel(); - - switch (saveResult.Result) - { - case OperationResultType.Failed: - case OperationResultType.FailedCannot: - case OperationResultType.FailedExceptionThrown: - case OperationResultType.NoOperation: - default: - notificationModel.AddErrorNotification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - null); //TODO: There is no specific failed to save error message AFAIK - break; - case OperationResultType.FailedCancelledByEvent: - notificationModel.AddErrorNotification( - Services.TextService.Localize("speechBubbles/operationCancelledHeader"), - Services.TextService.Localize("speechBubbles/operationCancelledText")); - break; - } - - return Request.CreateValidationErrorResponse(notificationModel); + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; } - - Logger.Info("User ID {UserId} rolled back content '{ContentId}' to version '{VersionId}'", userId, contentId, versionId); - //return ok - return Request.CreateResponse(HttpStatusCode.OK); + return Request.CreateValidationErrorResponse(notificationModel); } } } From 0f528a7a8903cdc744ac28b81621e6fe70f66c8e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 18 Oct 2018 15:25:17 +0200 Subject: [PATCH 186/278] disable roll back button when no version is selected --- .../common/infiniteeditors/rollback/rollback.controller.js | 3 +++ .../src/views/common/infiniteeditors/rollback/rollback.html | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index 63672879b0..eac4fab8a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -19,6 +19,7 @@ vm.variantVersions = []; vm.diff = null; vm.currentVersion = null; + vm.rollbackButtonDisabled = true; // find the current version for invariant nodes if($scope.model.node.variants.length === 1) { @@ -72,10 +73,12 @@ vm.previousVersion = data; vm.previousVersion.versionId = version.versionId; createDiff(vm.currentVersion, vm.previousVersion); + vm.rollbackButtonDisabled = false; }); } else { vm.diff = null; + vm.rollbackButtonDisabled = true; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 5faba3a3ef..6758f1df9b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -93,6 +93,7 @@ button-style="success" state="vm.rollbackButtonState" label-key="actions_rollback" + disabled="vm.rollbackButtonDisabled" action="vm.rollback()"> From 321313f134e34df9956994707ebcc4923bacf74b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:32:44 +0100 Subject: [PATCH 187/278] Remove rollback .aspx dialog --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - .../Umbraco/dialogs/rollBack.aspx | 98 ------------------- 2 files changed, 99 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index de52021220..d18dab1987 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -348,7 +348,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 84b2cc5cb8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,98 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - -
    - - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
    -
    -

    - -

    -
    - - - -
    -
    -
    -
    - - -
    From 1efe90ba1d362f7be6766f8c764f06f5c22c44e6 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 14:54:29 +0100 Subject: [PATCH 188/278] More rollback .aspx dialogs removed --- src/Umbraco.Web/Umbraco.Web.csproj | 9 - .../umbraco/dialogs/rollBack.aspx | 99 ----------- .../umbraco/dialogs/rollBack.aspx.cs | 156 ------------------ .../umbraco/dialogs/rollBack.aspx.designer.cs | 150 ----------------- 4 files changed, 414 deletions(-) delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5235728814..d87ae5dd1c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1312,12 +1312,6 @@ editPackage.aspx - - rollBack.aspx - - - rollBack.aspx - @@ -1402,9 +1396,6 @@ ASPXCodeBehind - - ASPXCodeBehind - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 88f106d269..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,99 +0,0 @@ -<%@ Page Language="c#" CodeBehind="rollBack.aspx.cs" MasterPageFile="../masterpages/umbracoDialog.Master" AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" %> - - - - - - - - -
    - - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
    -
    -

    - -

    -
    - - - -
    -
    -
    -
    - - -
    diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs deleted file mode 100644 index 54f2871149..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs +++ /dev/null @@ -1,156 +0,0 @@ -//TODO: REbuild this in angular and then remove - -//using System; -//using System.Collections; -//using System.ComponentModel; -//using System.Data; -//using System.Drawing; -//using System.Linq; -//using System.Web; -//using System.Web.SessionState; -//using System.Web.UI; -//using System.Web.UI.WebControls; -//using System.Web.UI.HtmlControls; -// -//using umbraco.cms.businesslogic.web; -//using umbraco.cms.businesslogic.property; - -//namespace umbraco.presentation.dialogs -//{ -// /// -// /// Summary description for rollBack. -// /// -// public partial class rollBack : UmbracoEnsuredPage -// { -// public rollBack() -// { -// CurrentApp = Constants.Applications.Content.ToString(); - -// } -// private Document currentDoc = new Document(int.Parse(helper.Request("nodeId"))); - -// protected void version_load(object sender, EventArgs e) { - -// if (allVersions.SelectedValue != "") -// { -// diffPanel.Visible = true; -// Document rollback = new Document(currentDoc.Id, new Guid(allVersions.SelectedValue)); - -// propertiesCompare.Text = "" + Services.TextService.Localize("general/name") + ":" + rollback.Text + ""; -// propertiesCompare.Text += "" + Services.TextService.Localize("content/createDate") + ":" + rollback.VersionDate.ToLongDateString() + " " + rollback.VersionDate.ToLongTimeString() + " " + Services.TextService.Localize("general/by") + ": " + rollback.User.Name + ""; - -// if (rbl_mode.SelectedValue == "diff") -// lt_notice.Text = Services.TextService.Localize("rollback/diffHelp"); -// else -// lt_notice.Text = Services.TextService.Localize("rollback/htmlHelp"); - - -// var props = rollback.GenericProperties; -// foreach (Property p in props) -// { -// try -// { - -// if (p.Value != null) -// { - -// //new property value... -// string thevalue = p.Value.ToString(); - -// if (rbl_mode.SelectedValue == "diff") -// { - -// //if display mode is set to diff... -// thevalue = library.StripHtml(p.Value.ToString()); -// Property cP = currentDoc.getProperty(p.PropertyType); -// if (cP != null && cP.Value != null) -// { - -// string cThevalue = library.StripHtml(cP.Value.ToString()); - -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + library.ReplaceLineBreaks(cms.businesslogic.utilities.Diff.Diff2Html(cThevalue, thevalue)) + ""; - - -// } -// else -// { -// //If no current version of the value... display with no diff. -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; -// } - - -// } -// else -// { -// //If display mode is html -// propertiesCompare.Text += "" + p.PropertyType.Name + ":" + thevalue + ""; -// } - -// //previewVersionContent.Controls.Add(new LiteralControl("

    " + p.PropertyType.Name + "
    ")); -// //previewVersionContent.Controls.Add(new LiteralControl(thevalue)); - -// } -// //previewVersionContent.Controls.Add(new LiteralControl("

    ")); -// } -// catch { } -// } - -// Button1.Visible = true; - - -// } -// else -// { -// diffPanel.Visible = false; -// Button1.Visible = false; -// } - -// } - -// protected void Page_Load(object sender, System.EventArgs e) -// { - -// if (String.IsNullOrEmpty(allVersions.SelectedValue)) -// rbl_mode.AutoPostBack = false; -// else -// rbl_mode.AutoPostBack = true; - -// currentVersionTitle.Text = currentDoc.Text; -// currentVersionMeta.Text = Services.TextService.Localize("content/createDate") + ": " + currentDoc.VersionDate.ToShortDateString() + " " + currentDoc.VersionDate.ToShortTimeString(); - -// if (!IsPostBack) { -// allVersions.Items.Add(new ListItem(Services.TextService.Localize("rollback/selectVersion")+ "...", "")); - -// foreach (DocumentVersionList dl in currentDoc.GetVersions()) -// { -// //we don't need to show the current version -// if (dl.Version == currentDoc.Version) -// continue; -// -// allVersions.Items.Add(new ListItem(dl.Text + " (" + Services.TextService.Localize("content/createDate") + ": " + dl.Date.ToShortDateString() + " " + dl.Date.ToShortTimeString() + ")", dl.Version.ToString())); -// } -// Button1.Text = Services.TextService.Localize("actions/rollback"); -// } -// } -// protected void doRollback_Click(object sender, System.EventArgs e) -// { -// if (allVersions.SelectedValue.Trim() != "") -// { -// Document d = new Document(int.Parse(helper.Request("nodeId"))); -// d.RollBack(new Guid(allVersions.SelectedValue), Security.CurrentUser); - -// BusinessLogic.Log.Add(BusinessLogic.LogTypes.RollBack, Security.CurrentUser, d.Id, "Version rolled back to revision '" + allVersions.SelectedValue + "'"); - -// Document rollback = new Document(d.Id, new Guid(allVersions.SelectedValue)); -// feedBackMsg.type = global::Umbraco.Web._Legacy.Controls.Feedback.feedbacktype.success; -// string[] vars = {rollback.Text, rollback.VersionDate.ToLongDateString()}; - -// feedBackMsg.Text = ui.Text("rollback", "documentRolledBack", vars, new global::umbraco.BusinessLogic.User(0)) + "

    " + Services.TextService.Localize("closeThisWindow") + ""; -// diffPanel.Visible = false; -// pl_buttons.Visible = false; -// -// ClientTools.ReloadLocationIfMatched(string.Format("/content/content/edit/{0}", d.Id)); -// } -// } -// } -//} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs deleted file mode 100644 index ab313afa40..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.designer.cs +++ /dev/null @@ -1,150 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.presentation.dialogs { - - - public partial class rollBack { - - ///

    - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// feedBackMsg control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback feedBackMsg; - - /// - /// pp_selectVersion control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pp_selectVersion; - - /// - /// pp_currentVersion control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_currentVersion; - - /// - /// currentVersionTitle control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal currentVersionTitle; - - /// - /// currentVersionMeta control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal currentVersionMeta; - - /// - /// pp_rollBackTo control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_rollBackTo; - - /// - /// allVersions control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.DropDownList allVersions; - - /// - /// pp_view control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel pp_view; - - /// - /// rbl_mode control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.RadioButtonList rbl_mode; - - /// - /// diffPanel control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Panel diffPanel; - - /// - /// lt_notice control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal lt_notice; - - /// - /// propertiesCompare control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal propertiesCompare; - - /// - /// pl_buttons control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlGenericControl pl_buttons; - - /// - /// Button1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button Button1; - } -} From 6850599ce99aa4b68de4e5a43790b01b1318d51f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Oct 2018 15:04:40 +0100 Subject: [PATCH 189/278] Remove the emtpy folders from CSProj in the legacy umbraco.presentation folder --- src/Umbraco.Web/Umbraco.Web.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d87ae5dd1c..63e497a1c4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1449,10 +1449,7 @@ umbraco_org_umbraco_update_CheckForUpgrade
    - - - - + From 0f2381a94da17302deac528578dee25b8803fcf5 Mon Sep 17 00:00:00 2001 From: Jamie Townsend Date: Sun, 14 Oct 2018 15:35:41 +0100 Subject: [PATCH 210/278] Merge multiple ng-if attributes --- .../src/views/users/views/user/details.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index d95e7cb80f..8d7cba5a78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -262,13 +262,12 @@
    - From f37f679031045fadeb28f8988d88a496af74cd33 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 19 Oct 2018 15:19:37 +0100 Subject: [PATCH 211/278] Adds ContentApps to the Composition extension methods --- src/Umbraco.Web/CompositionExtensions.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 332380009d..f33c8e98a8 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.ContentApps; // the namespace here is intentional - although defined in Umbraco.Web assembly, // this class should be visible when using Umbraco.Core.Components, alongside @@ -32,7 +33,15 @@ namespace Umbraco.Core.Components /// internal static ActionCollectionBuilder Actions(this Composition composition) => composition.Container.GetInstance(); - + + /// + /// Gets the content apps collection builder. + /// + /// The composition. + /// + public static ContentAppDefinitionCollectionBuilder ContentApps(this Composition composition) + => composition.Container.GetInstance(); + /// /// Gets the content finders collection builder. /// From 6996a06d64b270268267d655760e057fa82bfcd4 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Thu, 11 Oct 2018 19:46:43 +0200 Subject: [PATCH 212/278] Only add "Move" action once --- .../Trees/ContentTypeTreeController.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 33ae50dbbf..1544e5d2d0 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -117,21 +117,11 @@ namespace Umbraco.Web.Trees if (enableInheritedDocumentTypes) { menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))); - - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } } - else + //no move action if this is a child doc type + if (parent == null) { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias))); - //no move action if this is a child doc type - if (parent == null) - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); - } + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionMove.Instance.Alias)), true); } menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionCopy.Instance.Alias))); menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionExport.Instance.Alias)), true).ConvertLegacyMenuItem(new UmbracoEntity From 5dc67deeecde39a5f3fe5b6e70f485d45b5ca89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Kottal?= Date: Wed, 17 Oct 2018 11:22:55 +0200 Subject: [PATCH 213/278] adds the right description to UmbracoHelper.Concatenate --- src/Umbraco.Web/UmbracoHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index b06b786104..bb9bd2d50e 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1411,7 +1411,7 @@ namespace Umbraco.Web } /// - /// Will take the first non-null value in the collection and return the value of it. + /// Joins any number of int/string/objects into one string /// public string Concatenate(params object[] args) { From 7071b21db7d2251f03855d56f4c857eeed24d642 Mon Sep 17 00:00:00 2001 From: Carole Rennie Logan Date: Tue, 16 Oct 2018 19:50:11 +0100 Subject: [PATCH 214/278] Updating example content app manifest Including roles --- src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 3d4b24d359..d5f6c2b8c4 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -20,7 +20,8 @@ namespace Umbraco.Core.Manifest // show: [ // optional, default is always show // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types - // '+media/*' // show for all media types + // '+media/*', // show for all media types + // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, // ... From ef343f04a8e847a128e965733b34523819ab412d Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Thu, 11 Oct 2018 20:19:49 +0200 Subject: [PATCH 215/278] Only hide icons if hideIcons is set to 'true' --- .../src/views/components/buttons/umb-toggle.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html index 670b6c64f2..bc5c114bb6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-toggle.html @@ -6,9 +6,9 @@
    - +
    - +
    From 00cf8dcf05393e15f8a743763d8f9059f2a6979f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 11 Oct 2018 14:28:22 +0200 Subject: [PATCH 216/278] Reload the node when publishing all unpublished children --- .../lib/umbraco/LegacyUmbClientMgr.js | 3 ++- .../umbraco_client/Dialogs/PublishDialog.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 9fe887d0b6..223695322f 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -150,7 +150,8 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); sourceUrl: currentMenuNode.childNodesUrl, updateDefinition: function() { throw "'updateDefinition' method is not supported in Umbraco 7, consider upgrading to the new v7 APIs"; - } + }, + expanded: currentMenuNode.expanded === true }; //defined getters that will throw a not implemented/supported exception Object.defineProperty(legacyNode, "menu", { diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js index d034a07a4a..1781f71b27 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/PublishDialog.js @@ -37,12 +37,13 @@ }, startPublish: function() { this.processStatus("publishing"); - + + var includeUnpublished = self._koViewModel.includeUnpublished(); $.post(self._opts.restServiceLocation + "PublishDocument", JSON.stringify({ documentId: self._opts.documentId, publishDescendants: self._koViewModel.publishAll(), - includeUnpublished: self._koViewModel.includeUnpublished() + includeUnpublished: includeUnpublished }), function (e) { self._koViewModel.processStatus("complete"); @@ -59,7 +60,13 @@ //sync the tree UmbClientMgr.mainTree().setActiveTreeType('content'); - UmbClientMgr.mainTree().syncTree(self._opts.documentPath, true); + UmbClientMgr.mainTree().syncTree(self._opts.documentPath, true) + if (includeUnpublished) { + var node = UmbClientMgr.mainTree().getActionNode(); + if (node.expanded === true) { + UmbClientMgr.mainTree().reloadActionNode(); + } + } }); } }; From 4ef9c3f086d05f39f23f89070e10e366902e3d74 Mon Sep 17 00:00:00 2001 From: Chris Houston Date: Thu, 18 Oct 2018 00:58:02 -0400 Subject: [PATCH 217/278] When typing to configure some of the data types on a small width screen ( mobile or very narrow browser window ) the editing controls for some of the data types did not wrap underneath the titles and in some cases they disappeared completely. This adds a 100% width on small screen sizes and then reverts back to the original 60% width after 800px's. --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 7 ++++++- .../src/views/components/property/umb-property-editor.html | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 43f5aa5a9a..198885e6be 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -2,7 +2,12 @@ // Container styles // -------------------------------------------------- .umb-property-editor { - min-width:66.6%; + @media (max-width: 800px) { + width: 100%; + } + @media (min-width: 800px) { + min-width:66.6%; + } &-pull { float:left; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html index bbf70f7a8e..a22a15e808 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html @@ -1,3 +1,3 @@ -
    +
    From 5efb079f5623cfafcd251498b013076bc60ba44d Mon Sep 17 00:00:00 2001 From: sebastien-sougnez Date: Thu, 18 Oct 2018 17:18:53 +0200 Subject: [PATCH 218/278] Changed font-awesome verison from 4.2 to 4.7 --- src/Umbraco.Web.UI.Client/bower.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 80d7d9366b..5f94ecf629 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -31,7 +31,7 @@ "moment": "~2.10.3", "ace-builds": "^1.2.3", "clipboard": "1.7.1", - "font-awesome": "~4.2" + "font-awesome": "~4.7" }, "install": { "path": "lib-bower", @@ -43,11 +43,11 @@ ], "sources": { "moment": [ - "bower_components/moment/min/moment.min.js", - "bower_components/moment/min/moment-with-locales.js", - "bower_components/moment/min/moment-with-locales.min.js", - "bower_components/moment/locale/*.js" - ], + "bower_components/moment/min/moment.min.js", + "bower_components/moment/min/moment-with-locales.js", + "bower_components/moment/min/moment-with-locales.min.js", + "bower_components/moment/locale/*.js" + ], "underscore": [ "bower_components/underscore/underscore-min.js", "bower_components/underscore/underscore-min.map" From f4a9617487fee6043966aa8510855e661ddca568 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Thu, 18 Oct 2018 19:35:59 +0200 Subject: [PATCH 219/278] Improved the description for the "Icon" property --- src/Umbraco.Core/Models/IContentTypeBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 80e62f50cf..f1e10b03a2 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -21,7 +21,12 @@ namespace Umbraco.Core.Models string Description { get; set; } /// - /// Gets or Sets the Icon for the ContentType + /// Gets or sets the icon for the content type. The value is a CSS class name representing + /// the icon (eg. icon-home) along with an optional CSS class name representing the + /// color (eg. icon-blue). Put together, the value for this scenario would be + /// icon-home color-blue. + /// + /// If a class name for the color isn't specified, the icon color will default to black. /// string Icon { get; set; } @@ -119,4 +124,4 @@ namespace Umbraco.Core.Models /// bool MovePropertyType(string propertyTypeAlias, string propertyGroupName); } -} \ No newline at end of file +} From 5bd87efb3fcc20e1821f119629651f222f78da9c Mon Sep 17 00:00:00 2001 From: Pawel Bres Date: Thu, 18 Oct 2018 13:37:59 +0200 Subject: [PATCH 220/278] Updated URL to the Contributing page of v7 The old link was not working anymore and was pointing to 404 GitHub page --- .github/V8_GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index def923e0d0..62b376b0e7 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -23,7 +23,7 @@ We recommend running the site with the Visual Studio since you'll be able to rem ### Making code changes -* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md)_ +* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_ * Any .NET changes you make you just need to compile * Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built. * When making js changes, you should have the chrome developer tools open to ensure that cache is disabled From e1919e0c5253df9a6aa94df0df8a811499c80f35 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:54:17 +0200 Subject: [PATCH 221/278] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f1b415f3c5..ff234c71da 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -464,6 +464,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allow at root From 6f359b3d18d3231dd79a74358273605c2144336c Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:57:01 +0200 Subject: [PATCH 222/278] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ 1 file changed, 2 insertions(+) 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 31d157f54c..ed3a261979 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -464,6 +464,8 @@ Enter a message... Your username is usually your email #value or ?key=value + Enter alias... + Generating alias... Allow at root From 39edf4c6dd4dd0fc5c2b530f3b6577aa959ba44c Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 13:58:45 +0200 Subject: [PATCH 223/278] Added keys for "enterAlias" and "generatingAlias" --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index e68365ddb8..886662d17f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -443,6 +443,8 @@ Indtast din e-mail Indtast en besked... Dit brugernavn er typisk din e-mailadresse + Indtast alias... + Genererer alias... Tillad på rodniveau From 827bc19f6a4db824acf4af5161a6b234bcd7bc0a Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sun, 21 Oct 2018 14:03:22 +0200 Subject: [PATCH 224/278] Localized umb-generate-alias placeholder texts --- .../components/umbGenerateAlias.directive.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index 66e93d70d8..b3accc18b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -48,7 +48,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc **/ angular.module("umbraco.directives") - .directive('umbGenerateAlias', function ($timeout, entityResource) { + .directive('umbGenerateAlias', function ($timeout, entityResource, localizationService) { return { restrict: 'E', templateUrl: 'views/components/umb-generate-alias.html', @@ -67,7 +67,21 @@ angular.module("umbraco.directives") var updateAlias = false; scope.locked = true; - scope.placeholderText = "Enter alias..."; + + scope.labels = { + idle: "Enter alias...", + busy: "Generating alias...", + }; + + scope.placeholderText = scope.labels.idle; + + localizationService.localize('placeholders_enterAlias').then(function (value) { + scope.labels.idle = scope.placeholderText = value; + }); + + localizationService.localize('placeholders_generatingAlias').then(function (value) { + scope.labels.busy = value; + }); function generateAlias(value) { @@ -78,7 +92,7 @@ angular.module("umbraco.directives") if( value !== undefined && value !== "" && value !== null) { scope.alias = ""; - scope.placeholderText = "Generating Alias..."; + scope.placeholderText = scope.labels.busy; generateAliasTimeout = $timeout(function () { updateAlias = true; @@ -92,7 +106,7 @@ angular.module("umbraco.directives") } else { updateAlias = true; scope.alias = ""; - scope.placeholderText = "Enter alias..."; + scope.placeholderText = scope.labels.idle; } } From 90416a4d879e93f2c7dc69532597a6cb67c10b3f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Oct 2018 08:34:33 +0200 Subject: [PATCH 225/278] Add a custom content item binder for saving blueprints (fixes #2985) --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebApi/Binders/BlueprintItemBinder.cs | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6268759e29..4643cb2a1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,7 +576,7 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 66cce9b1bf..e021de0011 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -823,6 +823,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs new file mode 100644 index 0000000000..825c5b01c3 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class BlueprintItemBinder : ContentItemBinder + { + public BlueprintItemBinder(ApplicationContext applicationContext) + : base(applicationContext) + { + } + + /// + /// Constructor + /// + public BlueprintItemBinder() + : this(ApplicationContext.Current) + { + } + + protected override IContent GetExisting(ContentItemSave model) + { + return ApplicationContext.Services.ContentService.GetBlueprintById(Convert.ToInt32(model.Id)); + } + } +} \ No newline at end of file From 5da5df5bf9e9584d1675e38219bcd4f5f399f304 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Oct 2018 13:25:44 +0200 Subject: [PATCH 226/278] Hide "reset password" on the login screen if system emails can't be sent. --- .../src/views/common/dialogs/login.controller.js | 2 +- src/Umbraco.Web/Editors/BackOfficeServerVariables.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 9b703a0987..771718ac11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -207,7 +207,7 @@ } } - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 0444820bc4..6e27a8f8e5 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -311,6 +311,7 @@ namespace Umbraco.Web.Editors {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, + {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, } }, { From 01d9785097ec0dd9d55e274866f314a4af034b17 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Sun, 21 Oct 2018 16:00:11 +0200 Subject: [PATCH 227/278] 3364 - Suggestion: Make use of confirm action directive on repeatable textstring when deleting (#3365) --- .../less/components/umb-multiple-textbox.less | 17 ++++++++++++-- .../multipletextbox.controller.js | 22 ++++++++++++++++++- .../multipletextbox/multipletextbox.html | 19 +++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 21f59a3e2d..52cc7a9aaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,4 +1,17 @@ -.umb-multiple-textbox .textbox-wrapper { +.umb-multiple-textbox{ + &__confirm{ + position: relative; + + &-action{ + margin: 0; + padding: 2px; + background: transparent; + border: 0 none; + } + } +} + +.umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; } @@ -7,7 +20,7 @@ margin-bottom: 0; } -.umb-multiple-textbox .textbox-wrapper i { +.umb-multiple-textbox .textbox-wrapper i:not(.icon-delete, .icon-check) { margin-right: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index acee0f5ce9..9f190aab41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -2,6 +2,9 @@ var backspaceHits = 0; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = "-1"; + $scope.sortableOptions = { axis: 'y', containment: 'parent', @@ -89,6 +92,9 @@ }; $scope.remove = function (index) { + // Make sure not to trigger other prompts when remove is triggered + $scope.hidePrompt(); + var remainder = []; for (var x = 0; x < $scope.model.value.length; x++) { if (x !== index) { @@ -98,6 +104,20 @@ $scope.model.value = remainder; }; + $scope.showPrompt = function (idx, item){ + + var i = $scope.model.value.indexOf(item); + + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + } + + $scope.hidePrompt = function(){ + $scope.promptIsVisible = "-1"; + } + } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 123385d681..db98225e5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -5,11 +5,20 @@ - - - + +
    + + + + +
    +
    Date: Sat, 20 Oct 2018 12:16:24 +0200 Subject: [PATCH 228/278] Border color for .add-on should be @purple-l3 --- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 5926ea2163..bbedfe1a0b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -594,7 +594,7 @@ div.help { text-align: center; text-shadow: 0 1px 0 @white; background-color: @gray-10; - border: 1px solid @gray-8; + border: 1px solid @purple-l3; } .add-on, .btn, From 1de7b8f10fecdb37b45aab60cdc0ae3d0eff1b78 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sat, 20 Oct 2018 12:26:16 +0200 Subject: [PATCH 229/278]
     elements should at least have white-space:
     pre-wrap
    
    ---
     src/Umbraco.Web.UI.Client/src/less/hacks.less | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
    index 18439b554c..6505dc6e62 100644
    --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
    +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
    @@ -202,7 +202,7 @@ pre {
       //font-size: @baseFontSize - 1; // 14px to 13px
       color: @gray-2;
       line-height: @baseLineHeight;
    -  white-space: pre-line; // 1
    +  white-space: pre-wrap; // 1
       overflow-x: auto; // 1
       background-color: @gray-10;
       border: 1px solid @gray-8;
    @@ -222,4 +222,4 @@ pre {
         background-color: transparent;
         border: 0;
       }
    -}
    \ No newline at end of file
    +}
    
    From c401782d5e21bef81177789af7986e2652a46ef5 Mon Sep 17 00:00:00 2001
    From: Chris Houston 
    Date: Sun, 21 Oct 2018 07:27:39 -0700
    Subject: [PATCH 230/278] Added clean-css to the client side build process
     (#3371)
    
    ---
     src/Umbraco.Web.UI.Client/gulpfile.js       |   9 +-
     src/Umbraco.Web.UI.Client/package-lock.json | 204 +++++++++++---------
     src/Umbraco.Web.UI.Client/package.json      |   1 +
     3 files changed, 119 insertions(+), 95 deletions(-)
    
    diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js
    index 758f538656..d1bbfe65db 100644
    --- a/src/Umbraco.Web.UI.Client/gulpfile.js
    +++ b/src/Umbraco.Web.UI.Client/gulpfile.js
    @@ -6,21 +6,22 @@ var wrap = require("gulp-wrap-js");
     var sort = require('gulp-sort');
     var connect = require('gulp-connect');
     var open = require('gulp-open');
    -const babel = require("gulp-babel");
    +var babel = require("gulp-babel");
     var runSequence = require('run-sequence');
    -const imagemin = require('gulp-imagemin');
    +var imagemin = require('gulp-imagemin');
     
     var _ = require('lodash');
     var MergeStream = require('merge-stream');
     
     // js
    -const eslint = require('gulp-eslint');
    +var eslint = require('gulp-eslint');
     
     //Less + css
     var postcss = require('gulp-postcss');
     var less = require('gulp-less');
     var autoprefixer = require('autoprefixer');
     var cssnano = require('cssnano');
    +var cleanCss = require("gulp-clean-css");
     
     // Documentation
     var gulpDocs = require('gulp-ngdocs');
    @@ -49,7 +50,6 @@ function processJs(files, out) {
     }
     
     function processLess(files, out) {
    -
         var processors = [
              autoprefixer,
              cssnano({zindex: false})
    @@ -57,6 +57,7 @@ function processLess(files, out) {
     
         return gulp.src(files)
             .pipe(less())
    +        .pipe(cleanCss())
             .pipe(postcss(processors))
             .pipe(rename(out))
             .pipe(gulp.dest(root + targets.css));
    diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
    index 4b8cc3e6c2..deda717214 100644
    --- a/src/Umbraco.Web.UI.Client/package-lock.json
    +++ b/src/Umbraco.Web.UI.Client/package-lock.json
    @@ -1266,7 +1266,7 @@
         "array-slice": {
           "version": "1.1.0",
           "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
    -      "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=",
    +      "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==",
           "dev": true
         },
         "array-union": {
    @@ -1440,7 +1440,7 @@
         "base": {
           "version": "0.11.2",
           "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
    -      "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
    +      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
           "dev": true,
           "requires": {
             "cache-base": "^1.0.1",
    @@ -1464,7 +1464,7 @@
             "is-accessor-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
    +          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -1473,7 +1473,7 @@
             "is-data-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
    +          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -1482,7 +1482,7 @@
             "is-descriptor": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
    -          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
    +          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
               "dev": true,
               "requires": {
                 "is-accessor-descriptor": "^1.0.0",
    @@ -1680,7 +1680,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -1696,7 +1696,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -1822,7 +1822,7 @@
         "braces": {
           "version": "2.3.2",
           "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
    -      "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=",
    +      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
           "dev": true,
           "requires": {
             "arr-flatten": "^1.1.0",
    @@ -1948,7 +1948,7 @@
             },
             "uuid": {
               "version": "2.0.3",
    -          "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
    +          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
               "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
               "dev": true
             },
    @@ -2014,7 +2014,7 @@
         "cache-base": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
    -      "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
    +      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
           "dev": true,
           "requires": {
             "collection-visit": "^1.0.0",
    @@ -2225,7 +2225,7 @@
         "class-utils": {
           "version": "0.3.6",
           "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
    -      "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=",
    +      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
           "dev": true,
           "requires": {
             "arr-union": "^3.1.0",
    @@ -2245,6 +2245,23 @@
             }
           }
         },
    +    "clean-css": {
    +      "version": "4.2.1",
    +      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
    +      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
    +      "dev": true,
    +      "requires": {
    +        "source-map": "~0.6.0"
    +      },
    +      "dependencies": {
    +        "source-map": {
    +          "version": "0.6.1",
    +          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
    +          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
    +          "dev": true
    +        }
    +      }
    +    },
         "cli-cursor": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
    @@ -2458,7 +2475,7 @@
         },
         "commander": {
           "version": "2.8.1",
    -      "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
    +      "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
           "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
           "dev": true,
           "requires": {
    @@ -2700,7 +2717,7 @@
         "content-type": {
           "version": "1.0.4",
           "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
    -      "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
    +      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
           "dev": true
         },
         "convert-source-map": {
    @@ -3114,7 +3131,7 @@
                 },
                 "readable-stream": {
                   "version": "1.0.34",
    -              "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
    +              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
                   "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
                   "dev": true,
                   "requires": {
    @@ -3211,7 +3228,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -3227,7 +3244,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -3318,7 +3335,7 @@
             },
             "readable-stream": {
               "version": "1.0.34",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
    @@ -3379,7 +3396,7 @@
             },
             "readable-stream": {
               "version": "1.0.34",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
    @@ -3439,7 +3456,7 @@
             },
             "readable-stream": {
               "version": "1.0.34",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
               "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
               "dev": true,
               "requires": {
    @@ -3532,7 +3549,7 @@
         "define-property": {
           "version": "2.0.2",
           "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
    -      "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=",
    +      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
           "dev": true,
           "requires": {
             "is-descriptor": "^1.0.2",
    @@ -3542,7 +3559,7 @@
             "is-accessor-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
    +          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -3551,7 +3568,7 @@
             "is-data-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
    +          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -3560,7 +3577,7 @@
             "is-descriptor": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
    -          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
    +          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
               "dev": true,
               "requires": {
                 "is-accessor-descriptor": "^1.0.0",
    @@ -3828,7 +3845,7 @@
                 },
                 "readable-stream": {
                   "version": "1.0.34",
    -              "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
    +              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
                   "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
                   "dev": true,
                   "requires": {
    @@ -3925,7 +3942,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -3941,7 +3958,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -4034,7 +4051,7 @@
             "end-of-stream": {
               "version": "1.4.1",
               "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
    -          "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=",
    +          "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
               "dev": true,
               "requires": {
                 "once": "^1.4.0"
    @@ -4048,7 +4065,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -4064,7 +4081,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -4246,7 +4263,7 @@
         "errno": {
           "version": "0.1.7",
           "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
    -      "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=",
    +      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
           "dev": true,
           "optional": true,
           "requires": {
    @@ -4955,7 +4972,7 @@
         "extglob": {
           "version": "2.0.4",
           "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
    -      "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=",
    +      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
           "dev": true,
           "requires": {
             "array-unique": "^0.3.2",
    @@ -4989,7 +5006,7 @@
             "is-accessor-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
    +          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -4998,7 +5015,7 @@
             "is-data-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
    +          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -5007,7 +5024,7 @@
             "is-descriptor": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
    -          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
    +          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
               "dev": true,
               "requires": {
                 "is-accessor-descriptor": "^1.0.0",
    @@ -5505,8 +5522,7 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
    -          "dev": true,
    -          "optional": true
    +          "dev": true
             },
             "concat-map": {
               "version": "0.0.1",
    @@ -5516,8 +5532,7 @@
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
    -          "dev": true,
    -          "optional": true
    +          "dev": true
             },
             "core-util-is": {
               "version": "1.0.2",
    @@ -5634,8 +5649,7 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
    -          "dev": true,
    -          "optional": true
    +          "dev": true
             },
             "ini": {
               "version": "1.3.5",
    @@ -5647,7 +5661,6 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
    -          "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
    @@ -5773,8 +5786,7 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
    -          "dev": true,
    -          "optional": true
    +          "dev": true
             },
             "object-assign": {
               "version": "4.1.1",
    @@ -5786,7 +5798,6 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
    -          "optional": true,
               "requires": {
                 "wrappy": "1"
               }
    @@ -5908,7 +5919,6 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
    -          "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
    @@ -6049,7 +6059,7 @@
         },
         "get-stream": {
           "version": "3.0.0",
    -      "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
    +      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
           "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
           "dev": true,
           "optional": true
    @@ -6279,7 +6289,7 @@
         "global-modules": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
    -      "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
    +      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
           "dev": true,
           "requires": {
             "global-prefix": "^1.0.1",
    @@ -6391,7 +6401,7 @@
         },
         "got": {
           "version": "5.7.1",
    -      "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
    +      "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
           "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
           "dev": true,
           "requires": {
    @@ -6429,7 +6439,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -6517,6 +6527,18 @@
             }
           }
         },
    +    "gulp-clean-css": {
    +      "version": "3.10.0",
    +      "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz",
    +      "integrity": "sha512-7Isf9Y690o/Q5MVjEylH1H7L8WeZ89woW7DnhD5unTintOdZb67KdOayRgp9trUFo+f9UyJtuatV42e/+kghPg==",
    +      "dev": true,
    +      "requires": {
    +        "clean-css": "4.2.1",
    +        "plugin-error": "1.0.1",
    +        "through2": "2.0.3",
    +        "vinyl-sourcemaps-apply": "0.2.1"
    +      }
    +    },
         "gulp-concat": {
           "version": "2.6.1",
           "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz",
    @@ -6595,7 +6617,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -6611,7 +6633,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -6928,7 +6950,7 @@
             },
             "lodash": {
               "version": "2.4.1",
    -          "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz",
    +          "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz",
               "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=",
               "dev": true
             },
    @@ -8022,7 +8044,7 @@
         "is-absolute": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
    -      "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=",
    +      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
           "dev": true,
           "requires": {
             "is-relative": "^1.0.0",
    @@ -8126,7 +8148,7 @@
         "is-descriptor": {
           "version": "0.1.6",
           "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
    -      "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=",
    +      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
           "dev": true,
           "requires": {
             "is-accessor-descriptor": "^0.1.6",
    @@ -8137,7 +8159,7 @@
             "kind-of": {
               "version": "5.1.0",
               "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
    -          "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=",
    +          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
               "dev": true
             }
           }
    @@ -8268,7 +8290,7 @@
         },
         "is-obj": {
           "version": "1.0.1",
    -      "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
    +      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
           "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
           "dev": true
         },
    @@ -8360,7 +8382,7 @@
         "is-relative": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
    -      "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=",
    +      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
           "dev": true,
           "requires": {
             "is-unc-path": "^1.0.0"
    @@ -8417,7 +8439,7 @@
         "is-unc-path": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
    -      "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=",
    +      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
           "dev": true,
           "requires": {
             "unc-path-regex": "^0.1.2"
    @@ -8444,7 +8466,7 @@
         "is-windows": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
    -      "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=",
    +      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
           "dev": true
         },
         "is-zip": {
    @@ -8953,7 +8975,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -8969,7 +8991,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -9062,7 +9084,7 @@
         "livereload-js": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz",
    -      "integrity": "sha1-w6si6Kr1vzUF2A0JjLrWdyZUjJo=",
    +      "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==",
           "dev": true
         },
         "load-json-file": {
    @@ -9300,7 +9322,7 @@
         "lodash.merge": {
           "version": "4.6.1",
           "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
    -      "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=",
    +      "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==",
           "dev": true
         },
         "lodash.partialright": {
    @@ -9719,7 +9741,7 @@
         "make-iterator": {
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
    -      "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=",
    +      "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
           "dev": true,
           "requires": {
             "kind-of": "^6.0.2"
    @@ -9878,7 +9900,7 @@
         "micromatch": {
           "version": "3.1.10",
           "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
    -      "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=",
    +      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
           "dev": true,
           "requires": {
             "arr-diff": "^4.0.0",
    @@ -9941,7 +9963,7 @@
         "mixin-deep": {
           "version": "1.3.1",
           "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
    -      "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=",
    +      "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
           "dev": true,
           "requires": {
             "for-in": "^1.0.2",
    @@ -9951,7 +9973,7 @@
             "is-extendable": {
               "version": "1.0.1",
               "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
    -          "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=",
    +          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
               "dev": true,
               "requires": {
                 "is-plain-object": "^2.0.4"
    @@ -14077,7 +14099,7 @@
         "promise": {
           "version": "7.3.1",
           "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
    -      "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
    +      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
           "dev": true,
           "optional": true,
           "requires": {
    @@ -14290,7 +14312,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -14306,7 +14328,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -14513,7 +14535,7 @@
         "regex-not": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
    -      "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=",
    +      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
           "dev": true,
           "requires": {
             "extend-shallow": "^3.0.2",
    @@ -14749,7 +14771,7 @@
         "ret": {
           "version": "0.1.15",
           "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
    -      "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=",
    +      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
           "dev": true
         },
         "right-align": {
    @@ -15057,7 +15079,7 @@
         "set-value": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
    -      "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=",
    +      "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
           "dev": true,
           "requires": {
             "extend-shallow": "^2.0.1",
    @@ -15162,7 +15184,7 @@
         "snapdragon": {
           "version": "0.8.2",
           "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
    -      "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=",
    +      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
           "dev": true,
           "requires": {
             "base": "^0.11.1",
    @@ -15198,7 +15220,7 @@
         "snapdragon-node": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
    -      "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
    +      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
           "dev": true,
           "requires": {
             "define-property": "^1.0.0",
    @@ -15218,7 +15240,7 @@
             "is-accessor-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=",
    +          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -15227,7 +15249,7 @@
             "is-data-descriptor": {
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
    -          "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=",
    +          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
               "dev": true,
               "requires": {
                 "kind-of": "^6.0.0"
    @@ -15236,7 +15258,7 @@
             "is-descriptor": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
    -          "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=",
    +          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
               "dev": true,
               "requires": {
                 "is-accessor-descriptor": "^1.0.0",
    @@ -15249,7 +15271,7 @@
         "snapdragon-util": {
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
    -      "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
    +      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
           "dev": true,
           "requires": {
             "kind-of": "^3.2.0"
    @@ -15448,7 +15470,7 @@
         "split-string": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
    -      "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=",
    +      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
           "dev": true,
           "requires": {
             "extend-shallow": "^3.0.0"
    @@ -15574,7 +15596,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -15590,7 +15612,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -15765,7 +15787,7 @@
         },
         "strip-dirs": {
           "version": "1.1.1",
    -      "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
    +      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
           "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=",
           "dev": true,
           "requires": {
    @@ -15794,7 +15816,7 @@
             },
             "minimist": {
               "version": "1.2.0",
    -          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
    +          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
               "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
               "dev": true
             }
    @@ -15955,7 +15977,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -16272,7 +16294,7 @@
         "to-regex": {
           "version": "3.0.2",
           "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
    -      "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=",
    +      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
           "dev": true,
           "requires": {
             "define-property": "^2.0.2",
    @@ -16372,7 +16394,7 @@
         "type-is": {
           "version": "1.6.16",
           "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
    -      "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=",
    +      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
           "dev": true,
           "requires": {
             "media-typer": "0.3.0",
    @@ -16720,7 +16742,7 @@
         "vendors": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz",
    -      "integrity": "sha1-f8te759WI7FWvOqJ7DfWNnbyGAE=",
    +      "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==",
           "dev": true
         },
         "verror": {
    @@ -16777,7 +16799,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
    -          "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
    +          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
               "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "requires": {
    @@ -16793,7 +16815,7 @@
             "string_decoder": {
               "version": "1.1.1",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
    -          "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
    +          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
               "requires": {
                 "safe-buffer": "~5.1.0"
    @@ -16994,7 +17016,7 @@
         "websocket-extensions": {
           "version": "0.1.3",
           "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
    -      "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=",
    +      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
           "dev": true
         },
         "when": {
    diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
    index 21e0eafc33..e5948367da 100644
    --- a/src/Umbraco.Web.UI.Client/package.json
    +++ b/src/Umbraco.Web.UI.Client/package.json
    @@ -44,6 +44,7 @@
         "@babel/preset-env": "^7.1.0",
         "autoprefixer": "^6.5.0",
         "bower-installer": "^1.2.0",
    +    "gulp-clean-css": "3.10.0",
         "cssnano": "^3.7.6",
         "gulp": "^3.9.1",
         "gulp-babel": "^8.0.0-beta.2",
    
    From df2313f7ec0e8a1189f01243d4c5fddf35841715 Mon Sep 17 00:00:00 2001
    From: Anders Bjerner 
    Date: Sun, 21 Oct 2018 15:36:17 +0200
    Subject: [PATCH 231/278] umbRequestHelper.resourcePromise not supports the
     error message being a promise
    
    ---
     .../services/umbrequesthelper.service.js      | 919 +++++++++---------
     1 file changed, 464 insertions(+), 455 deletions(-)
    
    diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    index d950d39619..fa6099b226 100644
    --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    @@ -1,455 +1,464 @@
    -/**
    -* @ngdoc service
    -* @name umbraco.services.umbRequestHelper
    -* @description A helper object used for sending requests to the server
    -**/
    -function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) {
    -    return {
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path
    -         * 
    -         * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown
    -         */
    -        convertVirtualToAbsolutePath: function(virtualPath) {
    -            if (virtualPath.startsWith("/")) {
    -                return virtualPath;
    -            }
    -            if (!virtualPath.startsWith("~/")) {
    -                throw "The path " + virtualPath + " is not a virtual path";
    -            }
    -            if (!Umbraco.Sys.ServerVariables.application.applicationPath) { 
    -                throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath";
    -            }
    -            return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
    -        },
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will turn an array of key/value pairs or a standard dictionary into a query string
    -         * 
    -         * @param {Array} queryStrings An array of key/value pairs
    -         */
    -        dictionaryToQueryString: function (queryStrings) {
    -            
    -            if (angular.isArray(queryStrings)) {
    -                return _.map(queryStrings, function (item) {
    -                    var key = null;
    -                    var val = null;
    -                    for (var k in item) {
    -                        key = k;
    -                        val = item[k];
    -                        break;
    -                    }
    -                    if (key === null || val === null) {
    -                        throw "The object in the array was not formatted as a key/value pair";
    -                    }
    -                    return encodeURIComponent(key) + "=" + encodeURIComponent(val);
    -                }).join("&");
    -            }
    -            else if (angular.isObject(queryStrings)) {
    -
    -                //this allows for a normal object to be passed in (ie. a dictionary)
    -                return decodeURIComponent($.param(queryStrings));
    -            }
    -            
    -            throw "The queryString parameter is not an array or object of key value pairs";
    -        },
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#getApiUrl
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will return the webapi Url for the requested key based on the servervariables collection
    -         * 
    -         * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
    -         * @param {string} actionName The webapi action name 
    -         * @param {object} queryStrings Can be either a string or an array containing key/value pairs
    -         */
    -        getApiUrl: function (apiName, actionName, queryStrings) {
    -            if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
    -                throw "No server variables defined!";
    -            }
    -
    -            if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
    -                throw "No url found for api name " + apiName;
    -            }
    -
    -            return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
    -                (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
    -
    -        },
    -
    -        /**
    -         * @ngdoc function
    -         * @name umbraco.services.umbRequestHelper#resourcePromise
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This returns a promise with an underlying http call, it is a helper method to reduce
    -         *  the amount of duplicate code needed to query http resources and automatically handle any 
    -         *  Http errors. See /docs/source/using-promises-resources.md
    -         *
    -         * @param {object} opts A mixed object which can either be a string representing the error message to be
    -         *   returned OR an object containing either:
    -         *     { success: successCallback, errorMsg: errorMessage }
    -         *          OR
    -         *     { success: successCallback, error: errorCallback }
    -         *   In both of the above, the successCallback must accept these parameters: data, status, headers, config
    -         *   If using the errorCallback it must accept these parameters: data, status, headers, config
    -         *   The success callback must return the data which will be resolved by the deferred object.
    -         *   The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
    -         */
    -        resourcePromise: function (httpPromise, opts) {
    -            var deferred = $q.defer();
    -
    -            /** The default success callback used if one is not supplied in the opts */
    -            function defaultSuccess(data, status, headers, config) {
    -                //when it's successful, just return the data
    -                return data;
    -            }
    -
    -            /** The default error callback used if one is not supplied in the opts */
    -            function defaultError(data, status, headers, config) {
    -                return {
    -                    //NOTE: the default error message here should never be used based on the above docs!
    -                    errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
    -                    data: data,
    -                    status: status
    -                };
    -            }
    -
    -            //create the callbacs based on whats been passed in.
    -            var callbacks = {
    -                success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
    -                error: ((!opts || !opts.error) ? defaultError : opts.error)
    -            };
    -
    -            httpPromise.success(function (data, status, headers, config) {
    -
    -                //invoke the callback 
    -                var result = callbacks.success.apply(this, [data, status, headers, config]);
    -
    -                //when it's successful, just return the data
    -                deferred.resolve(result);
    -
    -            }).error(function (data, status, headers, config) {
    -
    -                //invoke the callback
    -                var result = callbacks.error.apply(this, [data, status, headers, config]);
    -
    -                //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
    -                if (status >= 500 && status < 600) {
    -
    -                    //show a ysod dialog
    -                    if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
    -                        eventsService.emit('app.ysod',
    -                        {
    -                            errorMsg: 'An error occured',
    -                            data: data
    -                        });
    -                    }
    -                    else {
    -                        //show a simple error notification                         
    -                        notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); - - - }); - - return deferred.promise; - - }, - - /** Used for saving media/content specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - - var deferred = $q.defer(); - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - if(data.tabs && data.tabs.length > 0) { - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - } - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + data.ExceptionMessage + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - - - }); - - return deferred.promise; - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - - $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': false }, - transformRequest: function (data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function (item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }). - success(function (data, status, headers, config) { - if (successCallback) { - successCallback.apply(this, [data, status, headers, config]); - } - }). - error(function (data, status, headers, config) { - if (failureCallback) { - failureCallback.apply(this, [data, status, headers, config]); - } - }); - }, - - /** - * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 - */ - downloadFile : function (httpPath) { - - var deferred = $q.defer(); - - // Use an arraybuffer - $http.get(httpPath, { responseType: 'arraybuffer' }) - .success(function (data, status, headers) { - - var octetStreamMime = 'application/octet-stream'; - var success = false; - - // Get the headers - headers = headers(); - - // Get the filename from the x-filename header or default to "download.bin" - var filename = headers['x-filename'] || 'download.bin'; - - // Determine the content type from the header or default to "application/octet-stream" - var contentType = headers['content-type'] || octetStreamMime; - - try { - // Try using msSaveBlob if supported - console.log("Trying saveBlob method ..."); - var blob = new Blob([data], { type: contentType }); - if (navigator.msSaveBlob) - navigator.msSaveBlob(blob, filename); - else { - // Try using other saveBlob implementations, if available - var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; - if (saveBlob === undefined) throw "Not supported"; - saveBlob(blob, filename); - } - console.log("saveBlob succeeded"); - success = true; - } catch (ex) { - console.log("saveBlob method failed with the following exception:"); - console.log(ex); - } - - if (!success) { - // Get the blob url creator - var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; - if (urlCreator) { - // Try to use a download link - var link = document.createElement('a'); - if ('download' in link) { - // Try to simulate a click - try { - // Prepare a blob URL - console.log("Trying download link method with simulated click ..."); - var blob = new Blob([data], { type: contentType }); - var url = urlCreator.createObjectURL(blob); - link.setAttribute('href', url); - - // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) - link.setAttribute("download", filename); - - // Simulate clicking the download link - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - console.log("Download link method with simulated click succeeded"); - success = true; - - } catch (ex) { - console.log("Download link method with simulated click failed with the following exception:"); - console.log(ex); - } - } - - if (!success) { - // Fallback to window.location method - try { - // Prepare a blob URL - // Use application/octet-stream when using window.location to force download - console.log("Trying download link method with window.location ..."); - var blob = new Blob([data], { type: octetStreamMime }); - var url = urlCreator.createObjectURL(blob); - window.location = url; - console.log("Download link method with window.location succeeded"); - success = true; - } catch (ex) { - console.log("Download link method with window.location failed with the following exception:"); - console.log(ex); - } - } - - } - } - - if (!success) { - // Fallback to window.open method - console.log("No methods worked for saving the arraybuffer, using last resort window.open"); - window.open(httpPath, '_blank', ''); - } - - deferred.resolve(); - }) - .error(function (data, status) { - console.log("Request failed with status: " + status); - - deferred.reject({ - errorMsg: "An error occurred downloading the file", - data: data, - status: status - }); - }); - - return deferred.promise; - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); +/** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function(virtualPath) { + if (virtualPath.startsWith("/")) { + return virtualPath; + } + if (!virtualPath.startsWith("~/")) { + throw "The path " + virtualPath + " is not a virtual path"; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw "The object in the array was not formatted as a key/value pair"; + } + return encodeURIComponent(key) + "=" + encodeURIComponent(val); + }).join("&"); + } + else if (angular.isObject(queryStrings)) { + + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + + throw "The queryString parameter is not an array or object of key value pairs"; + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { + throw "No server variables defined!"; + } + + if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { + throw "No url found for api name " + apiName; + } + + return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + + (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + + }, + + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + var deferred = $q.defer(); + + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + + var err = { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + data: data, + status: status + }; + + // if "opts" is a promise, we set "err.errorMsg" to be that promise + if (typeof(opts) == "object" && typeof(opts.then) == "function") { + err.errorMsg = opts; + } + + return err; + + } + + //create the callbacs based on whats been passed in. + var callbacks = { + success: ((!opts || !opts.success) ? defaultSuccess : opts.success), + error: ((!opts || !opts.error) ? defaultError : opts.error) + }; + + httpPromise.success(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.success.apply(this, [data, status, headers, config]); + + //when it's successful, just return the data + deferred.resolve(result); + + }).error(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.error.apply(this, [data, status, headers, config]); + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //show a ysod dialog + if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); + + + }); + + return deferred.promise; + + }, + + /** Used for saving media/content specifically */ + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + + + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + args.files[f].alias, args.files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + if(data.tabs && data.tabs.length > 0) { + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + } + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { + notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); + } + else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + data.ExceptionMessage + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + + + }); + + return deferred.promise; + }, + + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { + + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { throw "jsonData cannot be null"; } + + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } + }); + } + else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } + + + $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + transformRequest: function (data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function (item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } + else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + + //call the callback + if (transformCallback) { + transformCallback.apply(this, [data, formData]); + } + + return formData; + }, + data: jsonData + }). + success(function (data, status, headers, config) { + if (successCallback) { + successCallback.apply(this, [data, status, headers, config]); + } + }). + error(function (data, status, headers, config) { + if (failureCallback) { + failureCallback.apply(this, [data, status, headers, config]); + } + }); + }, + + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile : function (httpPath) { + + var deferred = $q.defer(); + + // Use an arraybuffer + $http.get(httpPath, { responseType: 'arraybuffer' }) + .success(function (data, status, headers) { + + var octetStreamMime = 'application/octet-stream'; + var success = false; + + // Get the headers + headers = headers(); + + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + + try { + // Try using msSaveBlob if supported + console.log("Trying saveBlob method ..."); + var blob = new Blob([data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) throw "Not supported"; + saveBlob(blob, filename); + } + console.log("saveBlob succeeded"); + success = true; + } catch (ex) { + console.log("saveBlob method failed with the following exception:"); + console.log(ex); + } + + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + console.log("Trying download link method with simulated click ..."); + var blob = new Blob([data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute("download", filename); + + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + console.log("Download link method with simulated click succeeded"); + success = true; + + } catch (ex) { + console.log("Download link method with simulated click failed with the following exception:"); + console.log(ex); + } + } + + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + console.log("Trying download link method with window.location ..."); + var blob = new Blob([data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + console.log("Download link method with window.location succeeded"); + success = true; + } catch (ex) { + console.log("Download link method with window.location failed with the following exception:"); + console.log(ex); + } + } + + } + } + + if (!success) { + // Fallback to window.open method + console.log("No methods worked for saving the arraybuffer, using last resort window.open"); + window.open(httpPath, '_blank', ''); + } + + deferred.resolve(); + }) + .error(function (data, status) { + console.log("Request failed with status: " + status); + + deferred.reject({ + errorMsg: "An error occurred downloading the file", + data: data, + status: status + }); + }); + + return deferred.promise; + } + }; +} +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); From d16bc7a4659009a37ca7aab9d567cfff6c2d94d2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Oct 2018 08:34:33 +0200 Subject: [PATCH 232/278] Add a custom content item binder for saving blueprints (fixes #2985) --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../WebApi/Binders/BlueprintItemBinder.cs | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6268759e29..4643cb2a1e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -576,7 +576,7 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = PostSaveInternal(contentItem, content => diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 66cce9b1bf..e021de0011 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -823,6 +823,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs new file mode 100644 index 0000000000..825c5b01c3 --- /dev/null +++ b/src/Umbraco.Web/WebApi/Binders/BlueprintItemBinder.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Binders +{ + internal class BlueprintItemBinder : ContentItemBinder + { + public BlueprintItemBinder(ApplicationContext applicationContext) + : base(applicationContext) + { + } + + /// + /// Constructor + /// + public BlueprintItemBinder() + : this(ApplicationContext.Current) + { + } + + protected override IContent GetExisting(ContentItemSave model) + { + return ApplicationContext.Services.ContentService.GetBlueprintById(Convert.ToInt32(model.Id)); + } + } +} \ No newline at end of file From 44fbff4410191068dec508d9b55bbef2a2894836 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Oct 2018 13:25:44 +0200 Subject: [PATCH 233/278] Hide "reset password" on the login screen if system emails can't be sent. --- .../src/views/common/dialogs/login.controller.js | 2 +- src/Umbraco.Web/Editors/BackOfficeServerVariables.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 9b703a0987..771718ac11 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -207,7 +207,7 @@ } } - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 0444820bc4..6e27a8f8e5 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors var keepOnlyKeys = new Dictionary { {"umbracoUrls", new[] {"authenticationApiBaseUrl", "serverVarsJs", "externalLoginsUrl", "currentUserApiBaseUrl"}}, - {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage"}}, + {"umbracoSettings", new[] {"allowPasswordReset", "imageFileTypes", "maxFileSize", "loginBackgroundImage", "canSendRequiredEmail"}}, {"application", new[] {"applicationPath", "cacheBuster"}}, {"isDebuggingEnabled", new string[] { }}, {"features", new [] {"disabledFeatures"}} @@ -311,6 +311,7 @@ namespace Umbraco.Web.Editors {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, {"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail}, + {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail}, } }, { From 7eecf230afacd0908140d4a82fb969734af069c9 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Sun, 21 Oct 2018 16:00:11 +0200 Subject: [PATCH 234/278] 3364 - Suggestion: Make use of confirm action directive on repeatable textstring when deleting (#3365) --- .../less/components/umb-multiple-textbox.less | 17 ++++++++++++-- .../multipletextbox.controller.js | 22 ++++++++++++++++++- .../multipletextbox/multipletextbox.html | 19 +++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 21f59a3e2d..52cc7a9aaf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,4 +1,17 @@ -.umb-multiple-textbox .textbox-wrapper { +.umb-multiple-textbox{ + &__confirm{ + position: relative; + + &-action{ + margin: 0; + padding: 2px; + background: transparent; + border: 0 none; + } + } +} + +.umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; } @@ -7,7 +20,7 @@ margin-bottom: 0; } -.umb-multiple-textbox .textbox-wrapper i { +.umb-multiple-textbox .textbox-wrapper i:not(.icon-delete, .icon-check) { margin-right: 5px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js index acee0f5ce9..9f190aab41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.controller.js @@ -2,6 +2,9 @@ var backspaceHits = 0; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = "-1"; + $scope.sortableOptions = { axis: 'y', containment: 'parent', @@ -89,6 +92,9 @@ }; $scope.remove = function (index) { + // Make sure not to trigger other prompts when remove is triggered + $scope.hidePrompt(); + var remainder = []; for (var x = 0; x < $scope.model.value.length; x++) { if (x !== index) { @@ -98,6 +104,20 @@ $scope.model.value = remainder; }; + $scope.showPrompt = function (idx, item){ + + var i = $scope.model.value.indexOf(item); + + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + } + + $scope.hidePrompt = function(){ + $scope.promptIsVisible = "-1"; + } + } -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); \ No newline at end of file +angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html index 123385d681..db98225e5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multipletextbox/multipletextbox.html @@ -5,11 +5,20 @@ -
    - - + +
    + + + + +
    +
    Date: Sat, 20 Oct 2018 12:16:24 +0200 Subject: [PATCH 235/278] Border color for .add-on should be @purple-l3 --- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 5926ea2163..bbedfe1a0b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -594,7 +594,7 @@ div.help { text-align: center; text-shadow: 0 1px 0 @white; background-color: @gray-10; - border: 1px solid @gray-8; + border: 1px solid @purple-l3; } .add-on, .btn, From bb3beb406d4e76e4a37124d09c0e6a37a92deea0 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Sat, 20 Oct 2018 12:26:16 +0200 Subject: [PATCH 236/278]
     elements should at least have white-space:
     pre-wrap
    
    ---
     src/Umbraco.Web.UI.Client/src/less/hacks.less | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less
    index 18439b554c..6505dc6e62 100644
    --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less
    +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less
    @@ -202,7 +202,7 @@ pre {
       //font-size: @baseFontSize - 1; // 14px to 13px
       color: @gray-2;
       line-height: @baseLineHeight;
    -  white-space: pre-line; // 1
    +  white-space: pre-wrap; // 1
       overflow-x: auto; // 1
       background-color: @gray-10;
       border: 1px solid @gray-8;
    @@ -222,4 +222,4 @@ pre {
         background-color: transparent;
         border: 0;
       }
    -}
    \ No newline at end of file
    +}
    
    From 954893cf2b81acfd7805ca4c9bd2d4cf78eb00a6 Mon Sep 17 00:00:00 2001
    From: Anders Bjerner 
    Date: Sun, 21 Oct 2018 15:36:17 +0200
    Subject: [PATCH 237/278] umbRequestHelper.resourcePromise not supports the
     error message being a promise
    
    ---
     .../services/umbrequesthelper.service.js      | 919 +++++++++---------
     1 file changed, 464 insertions(+), 455 deletions(-)
    
    diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    index d950d39619..fa6099b226 100644
    --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js
    @@ -1,455 +1,464 @@
    -/**
    -* @ngdoc service
    -* @name umbraco.services.umbRequestHelper
    -* @description A helper object used for sending requests to the server
    -**/
    -function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) {
    -    return {
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path
    -         * 
    -         * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown
    -         */
    -        convertVirtualToAbsolutePath: function(virtualPath) {
    -            if (virtualPath.startsWith("/")) {
    -                return virtualPath;
    -            }
    -            if (!virtualPath.startsWith("~/")) {
    -                throw "The path " + virtualPath + " is not a virtual path";
    -            }
    -            if (!Umbraco.Sys.ServerVariables.application.applicationPath) { 
    -                throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath";
    -            }
    -            return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
    -        },
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will turn an array of key/value pairs or a standard dictionary into a query string
    -         * 
    -         * @param {Array} queryStrings An array of key/value pairs
    -         */
    -        dictionaryToQueryString: function (queryStrings) {
    -            
    -            if (angular.isArray(queryStrings)) {
    -                return _.map(queryStrings, function (item) {
    -                    var key = null;
    -                    var val = null;
    -                    for (var k in item) {
    -                        key = k;
    -                        val = item[k];
    -                        break;
    -                    }
    -                    if (key === null || val === null) {
    -                        throw "The object in the array was not formatted as a key/value pair";
    -                    }
    -                    return encodeURIComponent(key) + "=" + encodeURIComponent(val);
    -                }).join("&");
    -            }
    -            else if (angular.isObject(queryStrings)) {
    -
    -                //this allows for a normal object to be passed in (ie. a dictionary)
    -                return decodeURIComponent($.param(queryStrings));
    -            }
    -            
    -            throw "The queryString parameter is not an array or object of key value pairs";
    -        },
    -
    -        /**
    -         * @ngdoc method
    -         * @name umbraco.services.umbRequestHelper#getApiUrl
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This will return the webapi Url for the requested key based on the servervariables collection
    -         * 
    -         * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
    -         * @param {string} actionName The webapi action name 
    -         * @param {object} queryStrings Can be either a string or an array containing key/value pairs
    -         */
    -        getApiUrl: function (apiName, actionName, queryStrings) {
    -            if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
    -                throw "No server variables defined!";
    -            }
    -
    -            if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
    -                throw "No url found for api name " + apiName;
    -            }
    -
    -            return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
    -                (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
    -
    -        },
    -
    -        /**
    -         * @ngdoc function
    -         * @name umbraco.services.umbRequestHelper#resourcePromise
    -         * @methodOf umbraco.services.umbRequestHelper
    -         * @function
    -         *
    -         * @description
    -         * This returns a promise with an underlying http call, it is a helper method to reduce
    -         *  the amount of duplicate code needed to query http resources and automatically handle any 
    -         *  Http errors. See /docs/source/using-promises-resources.md
    -         *
    -         * @param {object} opts A mixed object which can either be a string representing the error message to be
    -         *   returned OR an object containing either:
    -         *     { success: successCallback, errorMsg: errorMessage }
    -         *          OR
    -         *     { success: successCallback, error: errorCallback }
    -         *   In both of the above, the successCallback must accept these parameters: data, status, headers, config
    -         *   If using the errorCallback it must accept these parameters: data, status, headers, config
    -         *   The success callback must return the data which will be resolved by the deferred object.
    -         *   The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
    -         */
    -        resourcePromise: function (httpPromise, opts) {
    -            var deferred = $q.defer();
    -
    -            /** The default success callback used if one is not supplied in the opts */
    -            function defaultSuccess(data, status, headers, config) {
    -                //when it's successful, just return the data
    -                return data;
    -            }
    -
    -            /** The default error callback used if one is not supplied in the opts */
    -            function defaultError(data, status, headers, config) {
    -                return {
    -                    //NOTE: the default error message here should never be used based on the above docs!
    -                    errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
    -                    data: data,
    -                    status: status
    -                };
    -            }
    -
    -            //create the callbacs based on whats been passed in.
    -            var callbacks = {
    -                success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
    -                error: ((!opts || !opts.error) ? defaultError : opts.error)
    -            };
    -
    -            httpPromise.success(function (data, status, headers, config) {
    -
    -                //invoke the callback 
    -                var result = callbacks.success.apply(this, [data, status, headers, config]);
    -
    -                //when it's successful, just return the data
    -                deferred.resolve(result);
    -
    -            }).error(function (data, status, headers, config) {
    -
    -                //invoke the callback
    -                var result = callbacks.error.apply(this, [data, status, headers, config]);
    -
    -                //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
    -                if (status >= 500 && status < 600) {
    -
    -                    //show a ysod dialog
    -                    if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
    -                        eventsService.emit('app.ysod',
    -                        {
    -                            errorMsg: 'An error occured',
    -                            data: data
    -                        });
    -                    }
    -                    else {
    -                        //show a simple error notification                         
    -                        notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); - - - }); - - return deferred.promise; - - }, - - /** Used for saving media/content specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - - var deferred = $q.defer(); - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - if(data.tabs && data.tabs.length > 0) { - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - } - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + data.ExceptionMessage + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - - - }); - - return deferred.promise; - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - - $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': false }, - transformRequest: function (data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function (item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }). - success(function (data, status, headers, config) { - if (successCallback) { - successCallback.apply(this, [data, status, headers, config]); - } - }). - error(function (data, status, headers, config) { - if (failureCallback) { - failureCallback.apply(this, [data, status, headers, config]); - } - }); - }, - - /** - * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 - */ - downloadFile : function (httpPath) { - - var deferred = $q.defer(); - - // Use an arraybuffer - $http.get(httpPath, { responseType: 'arraybuffer' }) - .success(function (data, status, headers) { - - var octetStreamMime = 'application/octet-stream'; - var success = false; - - // Get the headers - headers = headers(); - - // Get the filename from the x-filename header or default to "download.bin" - var filename = headers['x-filename'] || 'download.bin'; - - // Determine the content type from the header or default to "application/octet-stream" - var contentType = headers['content-type'] || octetStreamMime; - - try { - // Try using msSaveBlob if supported - console.log("Trying saveBlob method ..."); - var blob = new Blob([data], { type: contentType }); - if (navigator.msSaveBlob) - navigator.msSaveBlob(blob, filename); - else { - // Try using other saveBlob implementations, if available - var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; - if (saveBlob === undefined) throw "Not supported"; - saveBlob(blob, filename); - } - console.log("saveBlob succeeded"); - success = true; - } catch (ex) { - console.log("saveBlob method failed with the following exception:"); - console.log(ex); - } - - if (!success) { - // Get the blob url creator - var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; - if (urlCreator) { - // Try to use a download link - var link = document.createElement('a'); - if ('download' in link) { - // Try to simulate a click - try { - // Prepare a blob URL - console.log("Trying download link method with simulated click ..."); - var blob = new Blob([data], { type: contentType }); - var url = urlCreator.createObjectURL(blob); - link.setAttribute('href', url); - - // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) - link.setAttribute("download", filename); - - // Simulate clicking the download link - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - console.log("Download link method with simulated click succeeded"); - success = true; - - } catch (ex) { - console.log("Download link method with simulated click failed with the following exception:"); - console.log(ex); - } - } - - if (!success) { - // Fallback to window.location method - try { - // Prepare a blob URL - // Use application/octet-stream when using window.location to force download - console.log("Trying download link method with window.location ..."); - var blob = new Blob([data], { type: octetStreamMime }); - var url = urlCreator.createObjectURL(blob); - window.location = url; - console.log("Download link method with window.location succeeded"); - success = true; - } catch (ex) { - console.log("Download link method with window.location failed with the following exception:"); - console.log(ex); - } - } - - } - } - - if (!success) { - // Fallback to window.open method - console.log("No methods worked for saving the arraybuffer, using last resort window.open"); - window.open(httpPath, '_blank', ''); - } - - deferred.resolve(); - }) - .error(function (data, status) { - console.log("Request failed with status: " + status); - - deferred.reject({ - errorMsg: "An error occurred downloading the file", - data: data, - status: status - }); - }); - - return deferred.promise; - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); +/** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ +function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function(virtualPath) { + if (virtualPath.startsWith("/")) { + return virtualPath; + } + if (!virtualPath.startsWith("~/")) { + throw "The path " + virtualPath + " is not a virtual path"; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw "The object in the array was not formatted as a key/value pair"; + } + return encodeURIComponent(key) + "=" + encodeURIComponent(val); + }).join("&"); + } + else if (angular.isObject(queryStrings)) { + + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + + throw "The queryString parameter is not an array or object of key value pairs"; + }, + + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { + throw "No server variables defined!"; + } + + if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { + throw "No url found for api name " + apiName; + } + + return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + + (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + + }, + + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + var deferred = $q.defer(); + + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + + var err = { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), + data: data, + status: status + }; + + // if "opts" is a promise, we set "err.errorMsg" to be that promise + if (typeof(opts) == "object" && typeof(opts.then) == "function") { + err.errorMsg = opts; + } + + return err; + + } + + //create the callbacs based on whats been passed in. + var callbacks = { + success: ((!opts || !opts.success) ? defaultSuccess : opts.success), + error: ((!opts || !opts.error) ? defaultError : opts.error) + }; + + httpPromise.success(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.success.apply(this, [data, status, headers, config]); + + //when it's successful, just return the data + deferred.resolve(result); + + }).error(function (data, status, headers, config) { + + //invoke the callback + var result = callbacks.error.apply(this, [data, status, headers, config]); + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //show a ysod dialog + if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); + + + }); + + return deferred.promise; + + }, + + /** Used for saving media/content specifically */ + postSaveContent: function (args) { + + if (!args.restApiUrl) { + throw "args.restApiUrl is a required argument"; + } + if (!args.content) { + throw "args.content is a required argument"; + } + if (!args.action) { + throw "args.action is a required argument"; + } + if (!args.files) { + throw "args.files is a required argument"; + } + if (!args.dataFormatter) { + throw "args.dataFormatter is a required argument"; + } + + + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); + + //save the data + this.postMultiPartRequest( + args.restApiUrl, + { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + args.files[f].alias, args.files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + if(data.tabs && data.tabs.length > 0) { + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + } + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { + notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); + } + else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', + { + errorMsg: 'An error occured', + data: data + }); + } + else { + //show a simple error notification + notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + data.ExceptionMessage + ""); + } + + } + + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + + + }); + + return deferred.promise; + }, + + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { + + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { throw "jsonData cannot be null"; } + + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } + }); + } + else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } + + + $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + transformRequest: function (data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function (item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } + else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + + //call the callback + if (transformCallback) { + transformCallback.apply(this, [data, formData]); + } + + return formData; + }, + data: jsonData + }). + success(function (data, status, headers, config) { + if (successCallback) { + successCallback.apply(this, [data, status, headers, config]); + } + }). + error(function (data, status, headers, config) { + if (failureCallback) { + failureCallback.apply(this, [data, status, headers, config]); + } + }); + }, + + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile : function (httpPath) { + + var deferred = $q.defer(); + + // Use an arraybuffer + $http.get(httpPath, { responseType: 'arraybuffer' }) + .success(function (data, status, headers) { + + var octetStreamMime = 'application/octet-stream'; + var success = false; + + // Get the headers + headers = headers(); + + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + + try { + // Try using msSaveBlob if supported + console.log("Trying saveBlob method ..."); + var blob = new Blob([data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) throw "Not supported"; + saveBlob(blob, filename); + } + console.log("saveBlob succeeded"); + success = true; + } catch (ex) { + console.log("saveBlob method failed with the following exception:"); + console.log(ex); + } + + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + console.log("Trying download link method with simulated click ..."); + var blob = new Blob([data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute("download", filename); + + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + console.log("Download link method with simulated click succeeded"); + success = true; + + } catch (ex) { + console.log("Download link method with simulated click failed with the following exception:"); + console.log(ex); + } + } + + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + console.log("Trying download link method with window.location ..."); + var blob = new Blob([data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + console.log("Download link method with window.location succeeded"); + success = true; + } catch (ex) { + console.log("Download link method with window.location failed with the following exception:"); + console.log(ex); + } + } + + } + } + + if (!success) { + // Fallback to window.open method + console.log("No methods worked for saving the arraybuffer, using last resort window.open"); + window.open(httpPath, '_blank', ''); + } + + deferred.resolve(); + }) + .error(function (data, status) { + console.log("Request failed with status: " + status); + + deferred.reject({ + errorMsg: "An error occurred downloading the file", + data: data, + status: status + }); + }); + + return deferred.promise; + } + }; +} +angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); From 96d7a776b7eddf8a21d4c59079adefafb79c292f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 21 Oct 2018 18:17:08 +0200 Subject: [PATCH 238/278] Removes dangling comma --- .../common/directives/components/umbGenerateAlias.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js index b3accc18b4..9b4bd17a12 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbGenerateAlias.directive.js @@ -70,7 +70,7 @@ angular.module("umbraco.directives") scope.labels = { idle: "Enter alias...", - busy: "Generating alias...", + busy: "Generating alias..." }; scope.placeholderText = scope.labels.idle; From 10526cfc81f3197336e6bdb543841aee5adfde50 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 21 Oct 2018 12:39:20 +0200 Subject: [PATCH 239/278] Fix #3368 by watching changes and explicitly flagging the form as dirty --- .../src/views/documenttypes/edit.controller.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 289bf607c3..cafe91e0eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -9,7 +9,7 @@ (function () { "use strict"; - function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService, angularHelper) { var vm = this; var localizeSaving = localizationService.localize("general_saving"); @@ -375,6 +375,15 @@ eventsService.unsubscribe(evts[e]); } }); + + // #3368 - changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty + $scope.$watch("vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.allowedTemplates.length + vm.contentType.isContainer", function (newVal, oldVal) { + if (oldVal === undefined) { + // still initializing, ignore + return; + } + angularHelper.getCurrentForm($scope).$setDirty(); + }); } angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController); From 2c14f396fbbe5c529e76068e64404d63e02e954b Mon Sep 17 00:00:00 2001 From: Tim Geyssens Date: Thu, 18 Oct 2018 21:28:46 +0200 Subject: [PATCH 240/278] Update FileSystemTree.cs Make sure it can find child items in root folders --- .../umbraco.presentation/umbraco/Trees/FileSystemTree.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index aed6009d86..06a5863da3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -42,8 +42,7 @@ namespace umbraco.cms.presentation.Trees if (!string.IsNullOrEmpty(this.NodeKey)) { orgPath = this.NodeKey; - path = IOHelper.MapPath(FilePath + orgPath); - orgPath += "/"; + path = IOHelper.MapPath(FilePath+ "/" + orgPath + "/"); } else { From 04aa1992667666d1768ef9d2afaa5483d76614b1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 21 Oct 2018 18:57:07 +0200 Subject: [PATCH 241/278] Makes sure the path on the tree is correct --- .../umbraco.presentation/umbraco/Trees/FileSystemTree.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs index 06a5863da3..35a5b3129a 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs @@ -8,6 +8,7 @@ using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.IO; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.cms.presentation.Trees @@ -41,8 +42,8 @@ namespace umbraco.cms.presentation.Trees string path = ""; if (!string.IsNullOrEmpty(this.NodeKey)) { - orgPath = this.NodeKey; - path = IOHelper.MapPath(FilePath+ "/" + orgPath + "/"); + orgPath = this.NodeKey.EnsureEndsWith('/'); + path = IOHelper.MapPath($"{FilePath.EnsureEndsWith('/')}{orgPath}"); } else { From dadc3ad7a7c4c5dcebe8edc18d6e041c7e5f708f Mon Sep 17 00:00:00 2001 From: James Coxhead Date: Thu, 18 Oct 2018 22:08:03 +0100 Subject: [PATCH 242/278] Fixes #3348 - Configure Umbraco.Dropdown.Flexible for new installations --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index d98bd81bfd..b8e605e63e 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -256,10 +256,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListMultipleAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); @@ -300,6 +300,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial //default's for MultipleMediaPickerAlias picker _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); + + // Defaults for single item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); + + // Defaults for multiple item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); } private void CreateUmbracoRelationTypeData() From 8c8d979eb20214e9c168ff831d5bc0a8afda98f3 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 11 Oct 2018 08:48:55 +0200 Subject: [PATCH 243/278] Style confirmations and alerts consistently across all sections (#3251) --- .../src/views/datatypes/move.html | 10 +++++++--- .../src/views/datatypes/rename.html | 6 ++++-- .../src/views/documenttypes/copy.html | 11 +++++++---- .../src/views/documenttypes/create.html | 12 ++++++++---- .../src/views/documenttypes/move.html | 11 +++++++---- .../src/views/documenttypes/rename.html | 6 ++++-- .../src/views/mediatypes/copy.html | 11 +++++++---- .../src/views/mediatypes/move.html | 10 +++++++--- .../src/views/mediatypes/rename.html | 6 ++++-- .../src/views/partialviewmacros/create.html | 6 ++++-- .../src/views/partialviews/create.html | 6 ++++-- .../src/views/scripts/create.html | 6 ++++-- 12 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html index f05b8a6c79..0546cbff44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -12,12 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

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

    {{error.data.message}}

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

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    -
    - {{currentNode.name}} was copied underneath {{target.name}}
    +
    + {{currentNode.name}} was copied underneath {{target.name}} +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 3519195848..b4818a51ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -50,8 +50,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    @@ -74,8 +76,10 @@ val-form-manager>
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index 1ed224b5ba..0af4cbfda7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -12,13 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

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

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html index fcc26b9928..77f50358c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html @@ -12,13 +12,16 @@
    -
    {{error.errorMsg}}
    -

    {{error.data.message}}

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

    {{error.data.message}}

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

    {{error.data.message}}

    +
    +
    {{error.errorMsg}}
    +
    {{error.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html index febaaab646..e2c7ab0af6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html @@ -61,8 +61,10 @@ val-form-manager>
    -
    {{vm.createFolderError.errorMsg}}
    -

    {{vm.createFolderError.data.message}}

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

    {{vm.createFolderError.data.message}}

    +
    +
    {{vm.createFolderError.errorMsg}}
    +
    {{vm.createFolderError.data.message}}
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html index f7d8b0382d..6cda42f21a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/create.html @@ -26,8 +26,10 @@ val-form-manager>
    -
    {{vm.createFolderError.errorMsg}}
    -

    {{vm.createFolderError.data.message}}

    +
    +
    {{vm.createFolderError.errorMsg}}
    +
    {{vm.createFolderError.data.message}}
    +
    From b86be5b854f0b3d7bf5964bf7cab52f08ffc659d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 11 Oct 2018 00:44:04 +0200 Subject: [PATCH 244/278] Fix color swatches title when using css classes for colors --- .../src/views/components/umb-color-swatches.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html index d038c9973c..2bc7ce9622 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-color-swatches.html @@ -1,6 +1,6 @@ 
    -