From 065cc69f51b3828dc8de1a1dbeb5d38def7eb85f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Feb 2019 09:45:36 +0100 Subject: [PATCH 01/17] #4477 - Changed the saved value from the radio button list to the text value. + Migration of old values --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../RadioButtonPropertyEditorsMigration.cs | 105 ++++++++++++++++++ .../RadioButtonListValueConverter.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 4 + .../radiobuttons/radiobuttons.html | 4 +- .../RadioButtonsPropertyEditor.cs | 2 +- 6 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index a9444a0918..c8247aa835 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -127,6 +127,7 @@ namespace Umbraco.Core.Migrations.Upgrade To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}"); To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); + To("{940FD19A-00A8-4D5C-B8FF-939143585726}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs new file mode 100644 index 0000000000..bcea3ec3b6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RadioButtonPropertyEditorsMigration : MigrationBase + { + public RadioButtonPropertyEditorsMigration(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + //need to convert the old drop down data types to use the new one + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias == "Umbraco.RadioButtonList")); + + foreach (var dataType in dataTypes) + { + ValueListConfiguration config; + + if (!dataType.Configuration.IsNullOrWhiteSpace()) + { + // parse configuration, and update everything accordingly + try + { + config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( + dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, + "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin() + .On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin() + .On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); + } + } + } + + private bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) + { + //Get the INT ids stored for this property/drop down + int[] ids = null; + if (propData.IntegerValue.HasValue) ids = new[] {propData.IntegerValue.Value}; + + //if there are INT ids, convert them to values based on the configuration + if (ids == null || ids.Length <= 0) return false; + + //map the ids to values + var values = new List(); + var canConvert = true; + + foreach (var id in ids) + { + var val = config.Items.FirstOrDefault(x => x.Id == id); + if (val != null) + values.Add(val.Value); + else + { + Logger.Warn( + "Could not find associated data type configuration for stored Id {DataTypeId}", id); + canConvert = false; + } + } + + if (!canConvert) return false; + + propData.VarcharValue = string.Join(",", values); + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + private class ValueListConfigurationEditor : ConfigurationEditor + { + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index 362c88d08c..fc7d3d42de 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -10,14 +10,14 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); public override Type GetPropertyValueType(PublishedPropertyType propertyType) - => typeof (int); + => typeof (string); public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType) => PropertyCacheLevel.Element; public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - var intAttempt = source.TryConvertTo(); + var intAttempt = source.TryConvertTo(); if (intAttempt.Success) return intAttempt.Result; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b80d607d4f..72dbb03a9b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -52,6 +52,9 @@ + + ..\Umbraco.Tests\bin\Debug\Umbraco.Web.dll + @@ -385,6 +388,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html index 7b9805d664..96367b641a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/radiobuttons/radiobuttons.html @@ -3,10 +3,10 @@
  • - \ No newline at end of file + diff --git a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs index 46069fec79..601728189c 100644 --- a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors /// /// A property editor to allow the individual selection of pre-defined items. /// - [DataEditor(Constants.PropertyEditors.Aliases.RadioButtonList, "Radio button list", "radiobuttons", ValueType = ValueTypes.Integer, Group="lists", Icon="icon-target")] + [DataEditor(Constants.PropertyEditors.Aliases.RadioButtonList, "Radio button list", "radiobuttons", ValueType = ValueTypes.String, Group="lists", Icon="icon-target")] public class RadioButtonsPropertyEditor : DataEditor { private readonly ILocalizedTextService _textService; From 354c45e0e2971a3352b328d83624744e09ac467f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Feb 2019 10:32:21 +0100 Subject: [PATCH 02/17] #4477 - also refresh cache after migration --- .../RadioButtonPropertyEditorsMigration.cs | 57 ++++++++++++++++++- .../RadioButtonListValueConverter.cs | 6 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs index bcea3ec3b6..4a5e2269b0 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs @@ -1,18 +1,27 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { public class RadioButtonPropertyEditorsMigration : MigrationBase { - public RadioButtonPropertyEditorsMigration(IMigrationContext context) + private readonly CacheRefresherCollection _cacheRefreshers; + private readonly IServerMessenger _serverMessenger; + + public RadioButtonPropertyEditorsMigration(IMigrationContext context, CacheRefresherCollection cacheRefreshers, IServerMessenger serverMessenger) : base(context) { + _cacheRefreshers = cacheRefreshers; + _serverMessenger = serverMessenger; } public override void Migrate() @@ -23,6 +32,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .From() .Where(x => x.EditorAlias == "Umbraco.RadioButtonList")); + var refreshCache = false; foreach (var dataType in dataTypes) { ValueListConfiguration config; @@ -60,15 +70,42 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 // persist changes foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; } } + + if (refreshCache) + { + var dataTypeCacheRefresher = _cacheRefreshers[ContentCacheRefresher.UniqueId]; + _serverMessenger.PerformRefreshAll(dataTypeCacheRefresher); + } + + } + + private void UpdateDataType(DataTypeDto dataType) + { + dataType.DbType = ValueStorageType.Nvarchar.ToString(); + Database.Update(dataType); } private bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) { //Get the INT ids stored for this property/drop down int[] ids = null; - if (propData.IntegerValue.HasValue) ids = new[] {propData.IntegerValue.Value}; + if (!propData.VarcharValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.VarcharValue); + } + else if (!propData.TextValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.TextValue); + } + else if (propData.IntegerValue.HasValue) + { + ids = new[] { propData.IntegerValue.Value }; + } //if there are INT ids, convert them to values based on the configuration if (ids == null || ids.Length <= 0) return false; @@ -101,5 +138,21 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 private class ValueListConfigurationEditor : ConfigurationEditor { } + + private int[] ConvertStringValues(string val) + { + var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var intVals = splitVals + .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) + .Where(x => x != int.MinValue) + .ToArray(); + + //only return if the number of values are the same (i.e. All INTs) + if (splitVals.Length == intVals.Length) + return intVals; + + return null; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index fc7d3d42de..b99cc7e0e3 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -17,10 +17,10 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - var intAttempt = source.TryConvertTo(); + var attempt = source.TryConvertTo(); - if (intAttempt.Success) - return intAttempt.Result; + if (attempt.Success) + return attempt.Result; return null; } From 4f53cfc85527ad8cdf02e25f7c66b4be5c64f8b0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 8 Feb 2019 14:18:24 +0100 Subject: [PATCH 03/17] #4477 - Added fixme for cache rebuild --- .../V_8_0_0/RadioButtonPropertyEditorsMigration.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs index 4a5e2269b0..2c4e601a9e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs @@ -14,14 +14,9 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { public class RadioButtonPropertyEditorsMigration : MigrationBase { - private readonly CacheRefresherCollection _cacheRefreshers; - private readonly IServerMessenger _serverMessenger; - - public RadioButtonPropertyEditorsMigration(IMigrationContext context, CacheRefresherCollection cacheRefreshers, IServerMessenger serverMessenger) + public RadioButtonPropertyEditorsMigration(IMigrationContext context) : base(context) { - _cacheRefreshers = cacheRefreshers; - _serverMessenger = serverMessenger; } public override void Migrate() @@ -78,8 +73,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (refreshCache) { - var dataTypeCacheRefresher = _cacheRefreshers[ContentCacheRefresher.UniqueId]; - _serverMessenger.PerformRefreshAll(dataTypeCacheRefresher); + //FIXME: trigger cache rebuild. Currently the data in the database tables is wrong. } } From 6fdada69eb2d96744fb1f640c194a0c626b5ef82 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 10 Feb 2019 18:55:16 +0100 Subject: [PATCH 04/17] Always show folder names in the media picker --- .../src/views/components/umb-media-grid.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index da94729562..1f5235146d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -4,7 +4,7 @@ -
    +
    {{item.name}}
    From 153b665d0a9b434a2f1269371381ef0e547b5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 11 Feb 2019 09:39:54 +0100 Subject: [PATCH 05/17] #4430 change grid-editor settings to use editorService --- .../grid/dialogs/editconfig.controller.js | 16 ++ .../grid/dialogs/editconfig.html | 53 +++++++ .../grid/dialogs/layoutconfig.controller.js | 28 +++- .../grid/dialogs/layoutconfig.html | 52 ++++++- .../grid/dialogs/rowconfig.controller.js | 26 +++- .../grid/dialogs/rowconfig.html | 51 ++++++- .../dialogs/rowdeleteconfirm.controller.js | 16 ++ .../grid/dialogs/rowdeleteconfirm.html | 38 ++++- .../grid/grid.prevalues.controller.js | 139 ++++++++---------- .../propertyeditors/grid/grid.prevalues.html | 24 --- 10 files changed, 330 insertions(+), 113 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.controller.js diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js new file mode 100644 index 0000000000..3441b6a060 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.controller.js @@ -0,0 +1,16 @@ +function EditConfigController($scope) { + + $scope.close = function() { + if($scope.model.close) { + $scope.model.close(); + } + } + + $scope.submit = function() { + if($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + } +} + +angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.EditConfigController", EditConfigController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html index fb541ecf84..9c42e04f75 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/editconfig.html @@ -1,3 +1,25 @@ +
    + + + + + +
    + + + + + + + + + +

    {{model.name}}

    @@ -12,3 +34,34 @@
    + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js index 566535fc98..4ad857b1e7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js @@ -1,10 +1,25 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController", - function ($scope) { + function ($scope, localizationService) { + + + function init() { + setTitle(); + } + + function setTitle() { + if (!$scope.model.title) { + localizationService.localize("grid_addGridLayout") + .then(function(data){ + $scope.model.title = data; + }); + } + } $scope.currentLayout = $scope.model.currentLayout; $scope.columns = $scope.model.columns; $scope.rows = $scope.model.rows; + $scope.currentSection = undefined; $scope.scaleUp = function(section, max, overflow){ var add = 1; @@ -57,9 +72,12 @@ angular.module("umbraco") template.sections.splice(index, 1); }; - $scope.closeSection = function(){ - $scope.currentSection = undefined; - }; + + $scope.close = function() { + if($scope.model.close) { + $scope.model.close(); + } + } $scope.$watch("currentLayout", function(layout){ if(layout){ @@ -71,4 +89,6 @@ angular.module("umbraco") $scope.availableLayoutSpace = $scope.columns - total; } }, true); + + init(); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html index 0e1a92a62c..6f70c5e7ef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html @@ -1,8 +1,31 @@
    + + + + + +
    + + + + + + + + + + + + +
    -

    @@ -32,7 +55,7 @@
    -
    +
    @@ -111,4 +134,27 @@
    -
    + + + + + + + + + + + + + + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js index 4e3dde50e4..1bb7516f30 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js @@ -1,5 +1,19 @@ -function RowConfigController($scope) { +function RowConfigController($scope, localizationService) { + function init() { + setTitle(); + } + + function setTitle() { + if (!$scope.model.title) { + localizationService.localize("grid_addRowConfiguration") + .then(function(data){ + $scope.model.title = data; + }); + } + } + + $scope.currentRow = $scope.model.currentRow; $scope.editors = $scope.model.editors; $scope.columns = $scope.model.columns; @@ -69,6 +83,12 @@ function RowConfigController($scope) { $scope.closeArea = function() { $scope.currentCell = undefined; }; + + $scope.close = function() { + if($scope.model.close) { + $scope.model.close(); + } + } $scope.nameChanged = false; var originalName = $scope.currentRow.name; @@ -93,6 +113,10 @@ function RowConfigController($scope) { } }, true); + + init(); + + } angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index c9a0d807ea..12d83f23fd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -1,8 +1,30 @@
    + + + + +
    + + + + + + + + + + + + +
    -

    @@ -14,7 +36,7 @@ - + @@ -54,7 +76,7 @@
    - + @@ -97,4 +119,27 @@
    + + + +
    +
    +
    + + + + + + + + +
    + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.controller.js new file mode 100644 index 0000000000..10b5601304 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.controller.js @@ -0,0 +1,16 @@ +function DeleteRowConfirmController($scope) { + + $scope.close = function() { + if($scope.model.close) { + $scope.model.close(); + } + } + + $scope.submit = function() { + if($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + } +} + +angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.DeleteRowConfirmController", DeleteRowConfirmController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html index c69ae2cb8f..2bf1f00b0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowdeleteconfirm.html @@ -1,4 +1,13 @@ -
    +
    + + + + + + + + +

    Warning!

    @@ -15,4 +24,31 @@ Are you sure?

    + + + +
    +
    +
    + + + + + + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js index 19057fa842..d38697d351 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.GridPrevalueEditorController", - function ($scope, gridService) { + function ($scope, gridService, editorService) { var emptyModel = { styles:[ @@ -89,28 +89,23 @@ angular.module("umbraco") }; $scope.model.value.templates.push(template); } + + var layoutConfigOverlay = { + currentLayout: template, + rows: $scope.model.value.layouts, + columns: $scope.model.value.columns, + view: "views/propertyEditors/grid/dialogs/layoutconfig.html", + size: "small", + submit: function(model) { + editorService.close(); + }, + close: function(model) { + editorService.close(); + } + }; - $scope.layoutConfigOverlay = {}; - $scope.layoutConfigOverlay.view = "views/propertyEditors/grid/dialogs/layoutconfig.html"; - $scope.layoutConfigOverlay.currentLayout = template; - $scope.layoutConfigOverlay.rows = $scope.model.value.layouts; - $scope.layoutConfigOverlay.columns = $scope.model.value.columns; - $scope.layoutConfigOverlay.show = true; + editorService.open(layoutConfigOverlay); - $scope.layoutConfigOverlay.submit = function(model) { - $scope.layoutConfigOverlay.show = false; - $scope.layoutConfigOverlay = null; - }; - - $scope.layoutConfigOverlay.close = function(oldModel) { - - //reset templates - $scope.model.value.templates = templatesCopy; - - $scope.layoutConfigOverlay.show = false; - $scope.layoutConfigOverlay = null; - } - }; $scope.deleteTemplate = function(index){ @@ -135,50 +130,44 @@ angular.module("umbraco") }; $scope.model.value.layouts.push(layout); } - - $scope.rowConfigOverlay = {}; - $scope.rowConfigOverlay.view = "views/propertyEditors/grid/dialogs/rowconfig.html"; - $scope.rowConfigOverlay.currentRow = layout; - $scope.rowConfigOverlay.editors = $scope.editors; - $scope.rowConfigOverlay.columns = $scope.model.value.columns; - $scope.rowConfigOverlay.show = true; - - $scope.rowConfigOverlay.submit = function(model) { - $scope.rowConfigOverlay.show = false; - $scope.rowConfigOverlay = null; - }; - - $scope.rowConfigOverlay.close = function(oldModel) { - $scope.model.value.layouts = layoutsCopy; - $scope.rowConfigOverlay.show = false; - $scope.rowConfigOverlay = null; + + var rowConfigOverlay = { + currentRow: layout, + editors: $scope.editors, + columns: $scope.model.value.columns, + view: "views/propertyEditors/grid/dialogs/rowconfig.html", + size: "small", + submit: function(model) { + editorService.close(); + }, + close: function(model) { + editorService.close(); + } }; + editorService.open(rowConfigOverlay); + }; //var rowDeletesPending = false; $scope.deleteLayout = function(index) { + + var rowDeleteOverlay = { + dialogData: { + rowName: $scope.model.value.layouts[index].name + }, + view: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html", + size: "small", + submit: function(model) { + $scope.model.value.layouts.splice(index, 1); + editorService.close(); + }, + close: function(model) { + editorService.close(); + } + }; - $scope.rowDeleteOverlay = {}; - $scope.rowDeleteOverlay.view = "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html"; - $scope.rowDeleteOverlay.dialogData = { - rowName: $scope.model.value.layouts[index].name - }; - $scope.rowDeleteOverlay.show = true; - - $scope.rowDeleteOverlay.submit = function(model) { - - $scope.model.value.layouts.splice(index, 1); - - $scope.rowDeleteOverlay.show = false; - $scope.rowDeleteOverlay = null; - }; - - $scope.rowDeleteOverlay.close = function(oldModel) { - $scope.rowDeleteOverlay.show = false; - $scope.rowDeleteOverlay = null; - }; - + editorService.open(rowDeleteOverlay); }; @@ -210,26 +199,22 @@ angular.module("umbraco") }; var editConfigCollection = function(configValues, title, callback) { + + var editConfigCollectionOverlay = { + config: configValues, + title: title, + view: "views/propertyeditors/grid/dialogs/editconfig.html", + size: "small", + submit: function(model) { + callback(model.config); + editorService.close(); + }, + close: function(model) { + editorService.close(); + } + }; - $scope.editConfigCollectionOverlay = {}; - $scope.editConfigCollectionOverlay.view = "views/propertyeditors/grid/dialogs/editconfig.html"; - $scope.editConfigCollectionOverlay.config = configValues; - $scope.editConfigCollectionOverlay.title = title; - $scope.editConfigCollectionOverlay.show = true; - - $scope.editConfigCollectionOverlay.submit = function(model) { - - callback(model.config); - - $scope.editConfigCollectionOverlay.show = false; - $scope.editConfigCollectionOverlay = null; - }; - - $scope.editConfigCollectionOverlay.close = function(oldModel) { - $scope.editConfigCollectionOverlay.show = false; - $scope.editConfigCollectionOverlay = null; - }; - + editorService.open(editConfigCollectionOverlay); }; $scope.editConfig = function() { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html index 539433821b..92d1a9ef26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.prevalues.html @@ -158,28 +158,4 @@
    - - - - - - - - - - - - From 14f878b690d489ace56dd104e05c757119b12ed0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Feb 2019 10:36:54 +0100 Subject: [PATCH 06/17] #4477 - Align Dropdown, checkbox list and radio button list --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../CheckBoxListPropertyEditorsMigration.cs | 150 ++++++++++++++++++ .../RadioButtonPropertyEditorsMigration.cs | 6 +- .../CheckboxListValueConverter.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../checkboxlist/checkboxlist.controller.js | 6 +- .../checkboxlist/checkboxlist.html | 2 +- .../PublishValuesMultipleValueEditor.cs | 13 +- .../FlexibleDropdownPropertyValueConverter.cs | 7 +- 9 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/CheckBoxListPropertyEditorsMigration.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index c8247aa835..d185c31a2b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -128,6 +128,7 @@ namespace Umbraco.Core.Migrations.Upgrade To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); To("{940FD19A-00A8-4D5C-B8FF-939143585726}"); + To("{C62C9BF1-833E-4866-B959-C8AB59E43E51}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/CheckBoxListPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/CheckBoxListPropertyEditorsMigration.cs new file mode 100644 index 0000000000..87a77963a8 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/CheckBoxListPropertyEditorsMigration.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class CheckBoxListPropertyEditorsMigration : MigrationBase + { + public CheckBoxListPropertyEditorsMigration(IMigrationContext context) + : base(context) + { + } + + public override void Migrate() + { + //need to convert the old drop down data types to use the new one + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList))); + + var refreshCache = false; + foreach (var dataType in dataTypes) + { + ValueListConfiguration config; + + if (!dataType.Configuration.IsNullOrWhiteSpace()) + { + // parse configuration, and update everything accordingly + try + { + config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( + dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, + "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin() + .On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin() + .On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; + } + } + + if (refreshCache) + { + //FIXME: trigger cache rebuild. Currently the data in the database tables is wrong. + } + + } + + private void UpdateDataType(DataTypeDto dataType) + { + dataType.DbType = ValueStorageType.Nvarchar.ToString(); + Database.Update(dataType); + } + + private bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) + { + //Get the INT ids stored for this property/drop down + int[] ids = null; + if (!propData.VarcharValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.VarcharValue); + } + else if (!propData.TextValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.TextValue); + } + else if (propData.IntegerValue.HasValue) + { + ids = new[] { propData.IntegerValue.Value }; + } + + //if there are INT ids, convert them to values based on the configuration + if (ids == null || ids.Length <= 0) return false; + + //map the ids to values + var values = new List(); + var canConvert = true; + + foreach (var id in ids) + { + var val = config.Items.FirstOrDefault(x => x.Id == id); + if (val != null) + values.Add(val.Value); + else + { + Logger.Warn( + "Could not find associated data type configuration for stored Id {DataTypeId}", id); + canConvert = false; + } + } + + if (!canConvert) return false; + + propData.VarcharValue = JsonConvert.SerializeObject(values); + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + private class ValueListConfigurationEditor : ConfigurationEditor + { + } + + private int[] ConvertStringValues(string val) + { + var splitVals = JsonConvert.DeserializeObject(val); + + var intVals = splitVals + .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) + .Where(x => x != int.MinValue) + .ToArray(); + + //only return if the number of values are the same (i.e. All INTs) + if (splitVals.Length == intVals.Length) + return intVals; + + return null; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs index 2c4e601a9e..e032cdea43 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var dataTypes = Database.Fetch(Sql() .Select() .From() - .Where(x => x.EditorAlias == "Umbraco.RadioButtonList")); + .Where(x => x.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList))); var refreshCache = false; foreach (var dataType in dataTypes) @@ -123,7 +123,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (!canConvert) return false; - propData.VarcharValue = string.Join(",", values); + propData.VarcharValue = values.FirstOrDefault() ?? string.Empty; propData.TextValue = null; propData.IntegerValue = null; return true; @@ -135,7 +135,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 private int[] ConvertStringValues(string val) { - var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var splitVals = new []{ val.Trim() }; var intVals = splitVals .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index 4062ed7311..3d69c37b8b 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters @@ -8,8 +9,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters [DefaultPropertyValueConverter] public class CheckboxListValueConverter : PropertyValueConverterBase { - private static readonly char[] Comma = { ',' }; - public override bool IsConverter(PublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); @@ -26,7 +25,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (string.IsNullOrEmpty(sourceString)) return Enumerable.Empty(); - return sourceString.Split(Comma, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + return JsonConvert.DeserializeObject(sourceString); } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 72dbb03a9b..da6c4a672c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -370,6 +370,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js index 9ef1b69aad..0699658711 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js @@ -54,7 +54,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro $scope.selectedItems = []; for (var i = 0; i < configItems.length; i++) { - var isChecked = _.contains($scope.model.value, configItems[i].id); + var isChecked = _.contains($scope.model.value, configItems[i].value); $scope.selectedItems.push({ checked: isChecked, key: configItems[i].id, @@ -68,11 +68,11 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro function (v) { return v === item.key; }); - + if (item.checked) { //if it doesn't exist in the model, then add it if (index < 0) { - $scope.model.value.push(item.key); + $scope.model.value.push(item.val); } } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html index 29760e3f5b..f777dc4797 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html @@ -6,7 +6,7 @@ {{item.val}} diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 8e0737dedd..13b060cf3d 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -1,12 +1,11 @@ using System; using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; -using Umbraco.Web.Composing; namespace Umbraco.Web.PropertyEditors { @@ -36,8 +35,8 @@ namespace Umbraco.Web.PropertyEditors /// public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var delimited = base.ToEditor(property, dataTypeService, culture, segment).ToString(); - return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var json = base.ToEditor(property, dataTypeService, culture, segment).ToString(); + return JsonConvert.DeserializeObject(json) ?? Array.Empty(); } /// @@ -55,9 +54,9 @@ namespace Umbraco.Web.PropertyEditors return null; } - var values = json.Select(item => item.Value()).ToList(); - //change to delimited - return string.Join(",", values); + var values = json.Select(item => item.Value()).ToArray(); + + return JsonConvert.SerializeObject(values); } } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs index d470f54662..43add34327 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/FlexibleDropdownPropertyValueConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; @@ -17,8 +18,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview) { - return source?.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Trim()).ToArray() - ?? Enumerable.Empty(); + if(source == null) return Array.Empty(); + + + return JsonConvert.DeserializeObject(source.ToString()) ?? Array.Empty(); } public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) From 5342e65f7c0597aed0c0b2933305d2a0b96d1290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 11 Feb 2019 12:46:23 +0100 Subject: [PATCH 07/17] V8: update selection colors for users section --- .../subheader/umb-editor-sub-header.less | 7 ++ .../less/components/users/umb-user-cards.less | 44 ++++++++----- .../src/less/tables.less | 9 +++ .../src/views/member/edit.html | 9 +-- .../propertyeditors/listview/listview.html | 14 ++-- .../src/views/users/user.html | 5 +- .../src/views/users/views/groups/groups.html | 29 +++++---- .../users/views/users/users.controller.js | 18 ++---- .../src/views/users/views/users/users.html | 64 +++++++++---------- 9 files changed, 112 insertions(+), 87 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 2b9f1e31a5..1aadb1c39b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -15,6 +15,13 @@ } } +.umb-editor-sub-header.--state-selection { + padding-left: 10px; + padding-right: 10px; + background-color: @pinkLight; + border-radius: 3px; +} + .umb-editor-sub-header.-umb-sticky-bar { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); transition: box-shadow 1s; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index de2dca5f91..e24f68078b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -8,6 +8,8 @@ box-sizing: border-box; max-width: 100%; display: flex; + position: relative; + user-select: none; } .umb-user-card:hover, @@ -15,6 +17,22 @@ outline: none; text-decoration: none !important; } +.umb-user-card.-selected { + &::before { + content: ""; + position: absolute; + z-index:2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid @ui-selected-border; + border-radius: 5px; + box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20); + pointer-events: none; + } + +} .umb-user-card__content { position: relative; @@ -30,9 +48,12 @@ max-width: 100%; } -.umb-user-card__content:hover, -.umb-user-card:focus .umb-user-card__content { - border-color: @turquoise; +.umb-user-card__goToUser { + &:hover { + .umb-user-card__name { + text-decoration: underline; + } + } } .umb-user-card__avatar { @@ -47,24 +68,13 @@ left: 10px; } + .umb-user-card__name { font-size: 15px; font-weight: bold; text-align: center; margin-bottom: 2px; - word-wrap: break-word; -} - -.umb-user-card__checkmark { - position: absolute; - top: 10px; - right: 10px; - display: none; -} - -.umb-user-card:hover .umb-user-card__checkmark, -.umb-user-card__checkmark--visible { - display: block; + word-wrap: break-word; } .umb-user-card__group { @@ -77,4 +87,4 @@ font-size: 13px; text-align: center; margin-top: auto; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/tables.less b/src/Umbraco.Web.UI.Client/src/less/tables.less index fa8a44ec47..09b6ea8a42 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tables.less +++ b/src/Umbraco.Web.UI.Client/src/less/tables.less @@ -62,6 +62,15 @@ table { } +.table tr > td:first-child { + border-left: 4px solid transparent; +} +.table tr.--selected > td:first-child { + border-left-color:@ui-selected-border; +} + + + // CONDENSED TABLE W/ HALF PADDING // ------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 310559b4d4..ee6e9c625c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -24,13 +24,13 @@
    {{ group.label }}
      - +
    - + @@ -38,8 +38,8 @@ - @@ -51,6 +51,7 @@ ng-if="page.listViewPath" type="link" href="#{{page.listViewPath}}" + button-style="link" label="Return to list" label-key="buttons_returnToList"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 9a638d91fa..2c70ba5730 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -6,7 +6,7 @@
    - + @@ -85,7 +85,7 @@ type="button" label="Clear selection" label-key="buttons_clearSelection" - button-style="selection" + button-style="white" action="clearSelection()" disabled="actionInProgress"> @@ -140,7 +140,7 @@ ng-if="options.allowBulkPublish && (buttonPermissions == null || buttonPermissions.canPublish)" style="margin-right: 5px;" type="button" - button-style="selection" + button-style="white" label-key="actions_publish" icon="icon-globe" action="publish()" @@ -153,7 +153,7 @@ ng-if="options.allowBulkUnpublish && (buttonPermissions == null || buttonPermissions.canUnpublish)" style="margin-right: 5px;" type="button" - button-style="selection" + button-style="white" label-key="actions_unpublish" icon="icon-block" action="unpublish()" @@ -166,7 +166,7 @@ ng-if="options.allowBulkCopy && (buttonPermissions == null || buttonPermissions.canCopy)" style="margin-right: 5px;" type="button" - button-style="selection" + button-style="white" label-key="actions_copy" icon="icon-documents" action="copy()" @@ -179,7 +179,7 @@ ng-if="options.allowBulkMove && (buttonPermissions == null || buttonPermissions.canMove)" style="margin-right: 5px;" type="button" - button-style="selection" + button-style="white" label-key="actions_move" icon="icon-enter" action="move()" @@ -191,7 +191,7 @@ - + - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html index deee402fd5..863d9658ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html @@ -2,13 +2,13 @@ - - + + @@ -40,6 +40,7 @@ @@ -54,6 +55,7 @@ type="button" label="Delete" label-key="general_delete" + button-style="white" icon="icon-trash" action="vm.deleteUserGroups()" size="xs"> @@ -64,7 +66,7 @@
    - Groups ({{vm.userGroups.length}}) + Groups ({{vm.userGroups.length}})
    @@ -76,19 +78,20 @@ - - - + + + + ng-click="vm.selectUserGroup(group, vm.selection, $event)" + style="width: 20px; height: 100px;"/> - -
    \ No newline at end of file +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 92ed10267d..6e624bfbe3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -199,14 +199,14 @@ vm.activeLayout = selectedLayout; } - function selectUser(user, selection, event) { + function selectUser(user, event) { - // prevent the current user to be selected - if (!user.isCurrentUser) { + // prevent the current user to be selected, why? + //if (!user.isCurrentUser) { if (user.selected) { - var index = selection.indexOf(user.id); - selection.splice(index, 1); + var index = vm.selection.indexOf(user.id); + vm.selection.splice(index, 1); user.selected = false; } else { user.selected = true; @@ -219,7 +219,7 @@ event.preventDefault(); event.stopPropagation(); } - } + //} } function clearSelection() { @@ -230,11 +230,7 @@ } function clickUser(user) { - if (vm.selection.length > 0) { - selectUser(user, vm.selection); - } else { - goToUser(user.id); - } + goToUser(user.id); } function disableUsers() { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 4b6f77e696..c270e823e6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -5,7 +5,7 @@ - + @@ -49,7 +49,7 @@ @@ -58,16 +58,16 @@ {{ vm.selection.length }} of {{ vm.users.length }} selected - + @@ -78,19 +78,19 @@ ng-if="vm.allowEnableUser" type="button" size="xs" - button-style="outline" + button-style="white" state="vm.enableUserButtonState" label-key="actions_enable" icon="icon-check" action="vm.enableUsers()"> - +
    - Status: + Status: {{ vm.getFilterName(vm.userStatesFilter) }} @@ -168,7 +168,7 @@
    - Order by: + Order by: {{ vm.getSortLabel(vm.usersOptions.orderBy, vm.usersOptions.orderDirection) }} @@ -192,20 +192,18 @@ @@ -332,7 +330,7 @@ Required {{addUserForm.name.errorMsg}} - + @@ -342,7 +340,7 @@ Required {{addUserForm.username.errorMsg}} - + @@ -356,7 +354,7 @@ - +
    - + Back to users - +
    - +

    - +
    @@ -511,7 +509,7 @@ - +
    Date: Mon, 11 Feb 2019 14:30:01 +0100 Subject: [PATCH 08/17] Disable button dont hide them + dont select current user --- .../users/views/users/users.controller.js | 67 +++++++++---------- .../src/views/users/views/users/users.html | 21 +++--- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 6862ffa1f6..c0e59be8e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -196,27 +196,23 @@ vm.activeLayout = selectedLayout; } - function selectUser(user, event) { - - // prevent the current user to be selected, why? - //if (!user.isCurrentUser) { - - if (user.selected) { - var index = vm.selection.indexOf(user.id); - vm.selection.splice(index, 1); - user.selected = false; - } else { - user.selected = true; - vm.selection.push(user.id); - } - - setBulkActions(vm.users); - - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - //} + function selectUser(user) { + + if (user.isCurrentUser) { + return; + } + + if (user.selected) { + var index = vm.selection.indexOf(user.id); + vm.selection.splice(index, 1); + user.selected = false; + } else { + user.selected = true; + vm.selection.push(user.id); + } + + setBulkActions(vm.users); + } function clearSelection() { @@ -621,18 +617,20 @@ var firstSelectedUserGroups; angular.forEach(users, function (user) { - + if (!user.selected) { return; } - + + console.log("CHECK") // if the current user is selected prevent any bulk actions with the user included if (user.isCurrentUser) { vm.allowDisableUser = false; vm.allowEnableUser = false; vm.allowUnlockUser = false; vm.allowSetUserGroup = false; - return; + console.log("IS CURRENT!") + return false; } if (user.userDisplayState && user.userDisplayState.key === "Disabled") { @@ -656,16 +654,17 @@ } // store the user group aliases of the first selected user - if (!firstSelectedUserGroups) { - firstSelectedUserGroups = user.userGroups.map(function (ug) { return ug.alias; }); - vm.allowSetUserGroup = true; - } else if (vm.allowSetUserGroup === true) { - // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing. - // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't - // really support that in the user group picker. - var userGroups = user.userGroups.map(function (ug) { return ug.alias; }); - if (_.difference(firstSelectedUserGroups, userGroups).length > 0) { - vm.allowSetUserGroup = false; + if (vm.allowSetUserGroup === true) { + if (!firstSelectedUserGroups) { + firstSelectedUserGroups = user.userGroups.map(function (ug) { return ug.alias; }); + } else { + // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing. + // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't + // really support that in the user group picker. + var userGroups = user.userGroups.map(function (ug) { return ug.alias; }); + if (_.difference(firstSelectedUserGroups, userGroups).length > 0) { + vm.allowSetUserGroup = false; + } } } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index b0a64fa6c0..cefc3c5c86 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -63,7 +63,7 @@ - + -
    +
    - + - {{user.name}} + {{user.name}} {{ userGroup.name }}, {{ user.formattedLastLogin }} From 118e31d6478ce11bdfeb23df9094a8f7baece548 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Feb 2019 14:03:22 +1100 Subject: [PATCH 09/17] removes/renames list view settings that don't make sense for v8 --- .../views/propertyeditors/listview/listview.controller.js | 1 - src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index a3fc33d2ea..44c0c4ae7b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -142,7 +142,6 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs } $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, filter: '', diff --git a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs index 6448a3354c..bcddc18369 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewConfiguration.cs @@ -13,7 +13,6 @@ namespace Umbraco.Web.PropertyEditors // initialize defaults PageSize = 10; - DisplayAtTabNumber = 1; OrderBy = "SortOrder"; OrderDirection = "asc"; @@ -43,9 +42,6 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] public int PageSize { get; set; } - [ConfigurationField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] - public int DisplayAtTabNumber { get; set; } - [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", Description = "The default sort order for the list")] public string OrderBy { get; set; } @@ -64,7 +60,7 @@ namespace Umbraco.Web.PropertyEditors Description = "The bulk actions that are allowed from the list view")] public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // TODO: managing defaults? - [ConfigurationField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")] + [ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")] public string TabName { get; set; } public class Property From c4eb76faad5b268914998b4ec1eb1863c9b89bc8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Feb 2019 15:02:27 +1100 Subject: [PATCH 10/17] Updates the checkbox html editor to store the string values, not int ids, renames the underlying confg editor and updates comments, updates the migration and we'll need to migrate the checkbox data too. --- .../Migrations/Upgrade/UmbracoPlan.cs | 2 +- ...tonAndCheckboxPropertyEditorsMigration.cs} | 110 ++++++++++-------- src/Umbraco.Core/Umbraco.Core.csproj | 5 +- .../checkboxlist/checkboxlist.controller.js | 8 +- .../checkboxlist/checkboxlist.html | 2 +- .../CheckBoxListPropertyEditor.cs | 7 +- .../DropDownFlexiblePropertyEditor.cs | 2 +- ...eValueEditor.cs => MultipleValueEditor.cs} | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 9 files changed, 78 insertions(+), 68 deletions(-) rename src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/{RadioButtonPropertyEditorsMigration.cs => RadioButtonAndCheckboxPropertyEditorsMigration.cs} (56%) rename src/Umbraco.Web/PropertyEditors/{PublishValuesMultipleValueEditor.cs => MultipleValueEditor.cs} (84%) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index ccacaa8893..ddc4289718 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -128,7 +128,7 @@ namespace Umbraco.Core.Migrations.Upgrade To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); To("98339BEF-E4B2-48A8-B9D1-D173DC842BBE"); - To("{940FD19A-00A8-4D5C-B8FF-939143585726}"); + To("{940FD19A-00A8-4D5C-B8FF-939143585726}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs similarity index 56% rename from src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs rename to src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs index 2c4e601a9e..ec60a22ecd 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioButtonAndCheckboxPropertyEditorsMigration.cs @@ -1,81 +1,98 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { - public class RadioButtonPropertyEditorsMigration : MigrationBase + public class RadioButtonAndCheckboxPropertyEditorsMigration : MigrationBase { - public RadioButtonPropertyEditorsMigration(IMigrationContext context) + public RadioButtonAndCheckboxPropertyEditorsMigration(IMigrationContext context) : base(context) { } public override void Migrate() { - //need to convert the old drop down data types to use the new one - var dataTypes = Database.Fetch(Sql() - .Select() - .From() - .Where(x => x.EditorAlias == "Umbraco.RadioButtonList")); + MigrateRadioButtons(); + MigrateCheckBoxes(); + } + + private void MigrateCheckBoxes() + { + //fixme: complete this + + var dataTypes = GetDataTypes(Constants.PropertyEditors.Aliases.CheckBoxList); + + + } + + private void MigrateRadioButtons() + { + var dataTypes = GetDataTypes(Constants.PropertyEditors.Aliases.RadioButtonList); var refreshCache = false; foreach (var dataType in dataTypes) { ValueListConfiguration config; - if (!dataType.Configuration.IsNullOrWhiteSpace()) + if (dataType.Configuration.IsNullOrWhiteSpace()) + continue; + + // parse configuration, and update everything accordingly + try { - // parse configuration, and update everything accordingly - try - { - config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( - dataType.Configuration); - } - catch (Exception ex) - { - Logger.Error( - ex, - "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", - dataType.Configuration); - - continue; - } - - // get property data dtos - var propertyDataDtos = Database.Fetch(Sql() - .Select() - .From() - .InnerJoin() - .On((pt, pd) => pt.Id == pd.PropertyTypeId) - .InnerJoin() - .On((dt, pt) => dt.NodeId == pt.DataTypeId) - .Where(x => x.DataTypeId == dataType.NodeId)); - - // update dtos - var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); - - // persist changes - foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); - - UpdateDataType(dataType); - refreshCache = true; + config = (ValueListConfiguration)new ValueListConfigurationEditor().FromDatabase( + dataType.Configuration); } + catch (Exception ex) + { + Logger.Error( + ex, + "Invalid radio button configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin() + .On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin() + .On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; } if (refreshCache) { //FIXME: trigger cache rebuild. Currently the data in the database tables is wrong. } + } + private List GetDataTypes(string editorAlias) + { + //need to convert the old drop down data types to use the new one + var dataTypes = Database.Fetch(Sql() + .Select() + .From() + .Where(x => x.EditorAlias == editorAlias)); + return dataTypes; } private void UpdateDataType(DataTypeDto dataType) @@ -123,7 +140,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (!canConvert) return false; - propData.VarcharValue = string.Join(",", values); + //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first + propData.VarcharValue = values.Count > 0 ? values[0] : string.Empty; propData.TextValue = null; propData.IntegerValue = null; return true; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4358fdbd42..d43aedc2d0 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -52,9 +52,6 @@ - - ..\Umbraco.Tests\bin\Debug\Umbraco.Web.dll - @@ -389,7 +386,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js index 9ef1b69aad..8897c59cef 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.controller.js @@ -42,7 +42,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro return f.checked; }), function(m) { - return m.key; + return m.value; }); //get all of the same values between the arrays var same = _.intersection($scope.model.value, selectedVals); @@ -54,7 +54,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro $scope.selectedItems = []; for (var i = 0; i < configItems.length; i++) { - var isChecked = _.contains($scope.model.value, configItems[i].id); + var isChecked = _.contains($scope.model.value, configItems[i].value); $scope.selectedItems.push({ checked: isChecked, key: configItems[i].id, @@ -66,13 +66,13 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListContro function changed(item) { var index = _.findIndex($scope.model.value, function (v) { - return v === item.key; + return v === item.value; }); if (item.checked) { //if it doesn't exist in the model, then add it if (index < 0) { - $scope.model.value.push(item.key); + $scope.model.value.push(item.value); } } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html index 29760e3f5b..6e9150d07c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/checkboxlist/checkboxlist.html @@ -4,7 +4,7 @@