diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index d773eed2e9..0c8bec8f2e 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors return result; } - private void ConvertItemsToJsonIfDetected(IDictionary result) + protected void ConvertItemsToJsonIfDetected(IDictionary result) { - //now we're going to try to see if any of the values are JSON, if they are we'll convert them to real JSON objects - // so they can be consumed as real json in angular! + // convert values that are Json to true Json objects that can be consumed by Angular var keys = result.Keys.ToArray(); for (var i = 0; i < keys.Length; i++) { - if (result[keys[i]] is string) + if ((result[keys[i]] is string) == false) continue; + + var asString = result[keys[i]].ToString(); + if (asString.DetectIsJson() == false) continue; + + try { - var asString = result[keys[i]].ToString(); - if (asString.DetectIsJson()) - { - try - { - var json = JsonConvert.DeserializeObject(asString); - result[keys[i]] = json; - } - catch - { - //swallow this exception, we thought it was json but it really isn't so continue returning a string - } - } + result[keys[i]] = JsonConvert.DeserializeObject(asString); + } + catch + { + // swallow this exception, we thought it was Json but it really isn't so continue returning a string } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs index 987640716b..b0d2e0809d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs @@ -1,13 +1,14 @@ using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters { [DefaultPropertyValueConverter] - [PropertyValueType(typeof(string))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - public class ColorPickerValueConverter : PropertyValueConverterBase + public class ColorPickerValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta { public override bool IsConverter(PublishedPropertyType propertyType) { @@ -18,11 +19,60 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return false; } + public Type GetPropertyValueType(PublishedPropertyType propertyType) + { + return UseLabel(propertyType) ? typeof(PickedColor) : typeof(string); + } + + public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) + { + return PropertyCacheLevel.Content; + } + + private bool UseLabel(PublishedPropertyType propertyType) + { + var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + PreValue preValue; + return preValues.PreValuesAsDictionary.TryGetValue("useLabel", out preValue) && preValue.Value == "1"; + } + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) { - // make sure it's a string - return source == null ? string.Empty : source.ToString(); + var useLabel = UseLabel(propertyType); + + if (source == null) return useLabel ? null : string.Empty; + + var ssource = source.ToString(); + if (ssource.DetectIsJson()) + { + try + { + var jo = JsonConvert.DeserializeObject(ssource); + if (useLabel) return new PickedColor(jo["value"].ToString(), jo["label"].ToString()); + return jo["value"].ToString(); + } + catch { /* not json finally */ } + } + + if (useLabel) return new PickedColor(ssource, ssource); + return ssource; + } + + public class PickedColor + { + public PickedColor(string color, string label) + { + Color = color; + Label = label; + } + + public string Color { get; private set; } + public string Label { get; private set; } + + public override string ToString() + { + return Color; + } } - } } diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index d23fdcbf84..cc45df2ce2 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -7,12 +7,22 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider', .controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) { + var isInit = $location.search().init; + if (isInit === "true") { + //do not continue, this is the first load of this new window, if this is passed in it means it's been + //initialized by the content editor and then the content editor will actually re-load this window without + //this flag. This is a required trick to get around chrome popup mgr. We don't want to double load preview.aspx + //since that will double prepare the preview documents + return; + } + $scope.isOpen = false; $scope.frameLoaded = false; $scope.enableCanvasdesigner = 0; $scope.googleFontFamilies = {}; - $scope.pageId = $location.search().id; - $scope.pageUrl = "../dialogs/Preview.aspx?id=" + $location.search().id; + var pageId = $location.search().id; + $scope.pageId = pageId; + $scope.pageUrl = "../dialogs/Preview.aspx?id=" + pageId; $scope.valueAreLoaded = false; $scope.devices = [ { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, 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 7d60a83b89..1a59c701d7 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 @@ -208,9 +208,9 @@ if (!$scope.busy) { // Chromes popup blocker will kick in if a window is opened - // outwith the initial scoped request. This trick will fix that. + // without the initial scoped request. This trick will fix that. // - var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview'); + var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); // Build the correct path so both /#/ and #/ work. var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js new file mode 100644 index 0000000000..30cfaf88b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpasswordtoggle.directive.js @@ -0,0 +1,23 @@ +(function () { + 'use strict'; + + function PasswordToggleDirective($compile) { + + var directive = { + restrict: 'A', + scope: {}, + link: function(scope, elem, attrs) { + scope.tgl = function () { elem.attr("type", (elem.attr("type") === "text" ? "password" : "text")); } + var lnk = angular.element("Toggle"); + $compile(lnk)(scope); + elem.wrap("
").after(lnk); + } + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 924f9b20bb..2fe214bf07 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -66,7 +66,7 @@ margin-bottom: auto; } -.login-overlay .form input[type="text"], +.login-overlay .form input[type="text"], .login-overlay .form input[type="password"], .login-overlay .form input[type="email"] { height: 36px; @@ -114,8 +114,44 @@ line-height: 36px; } -.login-overlay .text-error, -.login-overlay .text-info +.login-overlay .text-error, +.login-overlay .text-info { font-weight:bold; -} +} + +.password-toggle { + position: relative; + display: block; + user-select: none; + + input::-ms-clear, input::-ms-reveal { + display: none; + } + + a { + opacity: .5; + cursor: pointer; + display: inline-block; + position: absolute; + height: 1px; + width: 45px; + height: 75%; + font-size: 0; + background-repeat: no-repeat; + background-size: 50%; + background-position: center; + top: 0; + margin-left: -45px; + z-index: 1; + -webkit-tap-highlight-color: transparent; + } + + [type="text"] + a { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M29.6.4C29 0 28 0 27.4.4L21 6.8c-1.4-.5-3-.8-5-.8C9 6 3 10 0 16c1.3 2.6 3 4.8 5.4 6.5l-5 5c-.5.5-.5 1.5 0 2 .3.4.7.5 1 .5s1 0 1.2-.4l27-27C30 2 30 1 29.6.4zM13 10c1.3 0 2.4 1 2.8 2L12 15.8c-1-.4-2-1.5-2-2.8 0-1.7 1.3-3 3-3zm-9.6 6c1.2-2 2.8-3.5 4.7-4.7l.7-.2c-.4 1-.6 2-.6 3 0 1.8.6 3.4 1.6 4.7l-2 2c-1.6-1.2-3-2.7-4-4.4zM24 13.8c0-.8 0-1.7-.4-2.4l-10 10c.7.3 1.6.4 2.4.4 4.4 0 8-3.6 8-8z'/%3E%3Cpath fill='%23444' d='M26 9l-2.2 2.2c2 1.3 3.6 3 4.8 4.8-1.2 2-2.8 3.5-4.7 4.7-2.7 1.5-5.4 2.3-8 2.3-1.4 0-2.6 0-3.8-.4L10 25c2 .6 4 1 6 1 7 0 13-4 16-10-1.4-2.8-3.5-5.2-6-7z'/%3E%3C/svg%3E"); + } + + [type="password"] + a { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E"); + } +} 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 7fb3ac8f46..ffaa1a6a92 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -109,16 +109,48 @@ ul.color-picker li a { } /* pre-value editor */ - +/*.control-group.color-picker-preval:before { + content: ""; + display: inline-block; + vertical-align: middle; + height: 100%; +}*/ + +/*.control-group.color-picker-preval div.thumbnail { + display: inline-block; + vertical-align: middle; +}*/ +.control-group.color-picker-preval div.color-picker-prediv { + display: inline-block; + width: 60%; +} + .control-group.color-picker-preval pre { display: inline; margin-right: 20px; margin-left: 10px; + width: 50%; + white-space: nowrap; + overflow: hidden; + margin-bottom: 0; + vertical-align: middle; } +.control-group.color-picker-preval btn { + //vertical-align: middle; +} + +.control-group.color-picker-preval input[type="text"] { + min-width: 40%; + width: 40%; + display: inline-block; + margin-right: 20px; + margin-top: 1px; +} + .control-group.color-picker-preval label { - border:solid @white 1px; - padding:6px; + border: solid @white 1px; + padding: 6px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 61d04d30fb..a1913d76e7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -24,8 +24,8 @@ Your new password cannot be blank! - Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters - + Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters +
@@ -49,15 +49,15 @@
- + - + - +
{{ avatarFile.serverErrorMessage }}
@@ -69,7 +69,7 @@ ngf-multiple="false" ngf-pattern="{{avatarFile.acceptedFileTypes}}" ngf-max-size="{{ avatarFile.maxFileSize }}"> - + - +
@@ -149,7 +149,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index f14492e88a..7d847dbc83 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -1,25 +1,131 @@ -function ColorPickerController($scope) { - $scope.toggleItem = function (color) { - if ($scope.model.value == color) { - $scope.model.value = ""; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - else { - $scope.model.value = color; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - }; +function ColorPickerController($scope) { + + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + if ($scope.isConfigured) { + + for (var key in $scope.model.config.items) { + if (!$scope.model.config.items[key].hasOwnProperty("value")) + $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; + } + + $scope.model.useLabel = isTrue($scope.model.config.useLabel); + initActiveColor(); + } + + $scope.toggleItem = function (color) { + + var currentColor = $scope.model.value.hasOwnProperty("value") + ? $scope.model.value.value + : $scope.model.value; + + var newColor; + if (currentColor === color.value) { + // deselect + $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; + newColor = ""; + } + else { + // select + $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; + newColor = color.value; + } + + // this is required to re-validate + $scope.propertyForm.modelValue.$setViewValue(newColor); + }; + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) - $scope.validateMandatory = function () { + $scope.validateMandatory = function () { + var isValid = !$scope.model.validation.mandatory || ( + $scope.model.value != null + && $scope.model.value != "" + && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") + ); return { - isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""), + isValid: isValid, errorMsg: "Value cannot be empty", errorKey: "required" }; } - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + // A color is active if it matches the value and label of the model. + // If the model doesn't store the label, ignore the label during the comparison. + $scope.isActiveColor = function (color) { + + // no value + if (!$scope.model.value) + return false; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return $scope.model.value === color.value; + + return $scope.model.value.value === color.value && $scope.model.value.label === color.label; + }; + + // Finds the color best matching the model's color, + // and sets the model color to that one. This is useful when + // either the value or label was changed on the data type. + function initActiveColor() { + + // no value + if (!$scope.model.value) + return; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return; + + var modelColor = $scope.model.value.value; + var modelLabel = $scope.model.value.label; + + // Check for a full match or partial match. + var foundItem = null; + + // Look for a fully matching color. + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor && item.label == modelLabel) { + foundItem = item; + break; + } + } + + // Look for a color with a matching value. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor) { + foundItem = item; + break; + } + } + } + + // Look for a color with a matching label. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.label == modelLabel) { + foundItem = item; + break; + } + } + } + + // If a match was found, set it as the active color. + if (foundItem) { + $scope.model.value.value = foundItem.value; + $scope.model.value.label = foundItem.label; + } + } + + // figures out if a value is trueish enough + function isTrue(bool) { + return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; + } } angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController); 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 a493fffdd8..46b624adcc 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 @@ -5,10 +5,10 @@
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 a772f830c5..2b917e69f8 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 @@ -1,12 +1,13 @@
+ + -
-
{{item.value}}
+
#{{item.value}} - {{item.label}}
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 d31ac911a9..dd25741aeb 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 @@ -2,15 +2,17 @@ function ($scope, $timeout, assetsService, angularHelper, $element) { //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. var defaultColor = "000000"; - + var defaultLabel = null; + $scope.newColor = defaultColor; + $scope.newLavel = defaultLabel; $scope.hasError = false; assetsService.load([ //"lib/spectrum/tinycolor.js", - "lib/spectrum/spectrum.js" + "lib/spectrum/spectrum.js" ], $scope).then(function () { - var elem = $element.find("input"); + var elem = $element.find("input[name='newColor']"); elem.spectrum({ color: null, showInitial: false, @@ -21,7 +23,7 @@ clickoutFiresChange: true, hide: function (color) { //show the add butotn - $element.find(".btn.add").show(); + $element.find(".btn.add").show(); }, change: function (color) { angularHelper.safeApply($scope, function () { @@ -39,21 +41,41 @@ //make an array from the dictionary var items = []; for (var i in $scope.model.value) { - items.push({ - value: $scope.model.value[i], - id: i - }); + var oldValue = $scope.model.value[i]; + if (oldValue.hasOwnProperty("value")) { + items.push({ + value: oldValue.value, + label: oldValue.label, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + id: i + }); + } } //now make the editor model the array $scope.model.value = items; } + // ensure labels + for (var i = 0; i < $scope.model.value.length; i++) { + var item = $scope.model.value[i]; + item.label = item.hasOwnProperty("label") ? item.label : item.value; + } + + function validLabel(label) { + return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0; + } + $scope.remove = function (item, evt) { evt.preventDefault(); $scope.model.value = _.reject($scope.model.value, function (x) { - return x.value === item.value; + return x.value === item.value && x.label === item.label; }); }; @@ -63,15 +85,15 @@ evt.preventDefault(); if ($scope.newColor) { + var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor; var exists = _.find($scope.model.value, function(item) { - return item.value.toUpperCase() == $scope.newColor.toUpperCase(); + return item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase(); }); if (!exists) { - $scope.model.value.push({ value: $scope.newColor }); - //$scope.newColor = defaultColor; - // set colorpicker to default color - //var elem = $element.find("input"); - //elem.spectrum("set", $scope.newColor); + $scope.model.value.push({ + value: $scope.newColor, + label: newLabel + }); $scope.hasError = false; return; } 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 421aaf0d40..381db5a38c 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 @@ -48,14 +48,18 @@ - {{currentCell.grid}} + {{currentCell.grid}}
+ + + + - + Delete 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 bb1da209bf..a282334cb5 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 @@ -81,6 +81,7 @@ angular.module("umbraco") var notIncludedRte = []; var cancelMove = false; + var startingArea; $scope.sortableOptionsCell = { distance: 10, @@ -112,9 +113,11 @@ angular.module("umbraco") }, over: function (event, ui) { - var allowedEditors = $(event.target).scope().area.allowed; + var area = $(event.target).scope().area; + var allowedEditors = area.allowed; - if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) { + if (($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) || + (startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1)) { $scope.$apply(function () { $(event.target).scope().area.dropNotAllowed = true; @@ -168,6 +171,10 @@ angular.module("umbraco") start: function (e, ui) { + //Get the starting area for reference + var area = $(e.target).scope().area; + startingArea = area; + // fade out control when sorting ui.item.context.style.display = "block"; ui.item.context.style.opacity = "0.5"; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html index 4ddca47489..c2513b9760 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.html @@ -224,7 +224,7 @@ -
+
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 06bdfe2e99..fa2da2f9e2 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 @@ -65,7 +65,9 @@ ng-repeat="area in layout.areas | filter:zeroWidthFilter" ng-style="{width: percentage(area.grid) + '%', 'max-width': '100%'}"> -
+
+

{{area.maxItems}}

+
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 4a189642b6..16ed8de775 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1260,6 +1260,9 @@ Mange hilsner fra Umbraco robotten Vælg standard er tilføjet + Maksimalt emner + Efterlad blank eller sat til 0 ubegrænset for + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 7e6a4fcc22..a3b12c2e78 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1419,6 +1419,8 @@ To manage your website, simply open the Umbraco back office and start adding con Allow all editors Allow all row configurations + Leave blank or set to 0 for unlimited + Maximum items Set as default Choose extra Choose default 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 58c2d2eefc..6fc09bb493 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1406,6 +1406,8 @@ To manage your website, simply open the Umbraco back office and start adding con Allow all editors Allow all row configurations + Maximum items + Leave blank or set to 0 for unlimited Set as default Choose extra Choose default diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index 2de944a7df..99283482aa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -789,6 +789,9 @@ Permitir todos los controles de edición Permitir todas las configuraciones de fila + Artículos máximos + Laat dit leeg of is ingesteld op -1 voor onbeperkt + Dejar en blanco o se establece en 0 para ilimitada Campo opcional diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index c480373fd6..7b170cdc41 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -1077,7 +1077,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Alle editors toelaten Alle rijconfiguraties toelaten - Instellen als standaard + Maximale artikelen + Laat dit leeg of is ingesteld op -1 voor onbeperkt + Instellen als standaard Kies extra Kies standaard zijn toegevoegd diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 511b33f812..d53ee84e7c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -643,9 +643,7 @@ namespace Umbraco.Web.Editors ShowMessageForPublishStatus(publishStatus.Result, display); break; } - - UpdatePreviewContext(contentItem.PersistedContent.Id); - + //If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! @@ -875,24 +873,6 @@ namespace Umbraco.Web.Editors } } - /// - /// Checks if the user is currently in preview mode and if so will update the preview content for this item - /// - /// - private void UpdatePreviewContext(int contentId) - { - var previewId = Request.GetPreviewCookieValue(); - if (previewId.IsNullOrWhiteSpace()) return; - Guid id; - if (Guid.TryParse(previewId, out id)) - { - var d = new Document(contentId); - var pc = new PreviewContent(UmbracoUser, id, false); - pc.PrepareDocument(UmbracoUser, d, true); - pc.SavePreviewSet(); - } - } - /// /// Maps the dto property values to the persisted model /// diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs index e7750c4c65..a504ed0431 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs @@ -1,9 +1,12 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -11,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors { internal class ColorListPreValueEditor : ValueListPreValueEditor { - + public ColorListPreValueEditor() { var field = Fields.First(); @@ -23,14 +26,98 @@ namespace Umbraco.Web.PropertyEditors //change the label field.Name = "Add color"; //need to have some custom validation happening here - field.Validators.Add(new ColorListValidator()); + field.Validators.Add(new ColorListValidator()); + + Fields.Insert(0, new PreValueField + { + Name = "Include labels?", + View = "boolean", + Key = "useLabel", + Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string." + }); } public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) { var dictionary = persistedPreVals.FormatAsDictionary(); - var arrayOfVals = dictionary.Select(item => item.Value).ToList(); - return new Dictionary { { "items", arrayOfVals.ToDictionary(x => x.Id, x => x.Value) } }; + var items = dictionary + .Where(x => x.Key != "useLabel") + .ToDictionary(x => x.Value.Id, x => x.Value.Value); + + var items2 = new Dictionary(); + foreach (var item in items) + { + if (item.Value.DetectIsJson() == false) + { + items2[item.Key] = item.Value; + continue; + } + + try + { + items2[item.Key] = JsonConvert.DeserializeObject(item.Value); + } + catch + { + // let's say parsing Json failed, so what we have is the string - build json + items2[item.Key] = new JObject { { "color", item.Value }, { "label", item.Value } }; + } + } + + var result = new Dictionary { { "items", items2 } }; + var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1"; + if (useLabel) + result["useLabel"] = dictionary["useLabel"].Value; + + return result; + } + + public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) + { + var val = editorValue["items"] as JArray; + var result = new Dictionary(); + if (val == null) return result; + + try + { + object useLabelObj; + var useLabel = false; + if (editorValue.TryGetValue("useLabel", out useLabelObj)) + { + useLabel = useLabelObj is string && (string) useLabelObj == "1"; + result["useLabel"] = new PreValue(useLabel ? "1" : "0"); + } + + // get all non-empty values + var index = 0; + foreach (var preValue in val.OfType() + .Where(x => x["value"] != null) + .Select(x => + { + var idString = x["id"] == null ? "0" : x["id"].ToString(); + int id; + if (int.TryParse(idString, out id) == false) id = 0; + + var color = x["value"].ToString(); + if (string.IsNullOrWhiteSpace(color)) return null; + + var label = x["label"].ToString(); + return new PreValue(id, useLabel + ? JsonConvert.SerializeObject(new { value = color, label = label }) + : color); + }) + .WhereNotNull()) + { + result.Add(index.ToInvariantString(), preValue); + index++; + } + } + catch (Exception ex) + { + LogHelper.Error("Could not deserialize the posted value: " + val, ex); + } + + return result; } internal class ColorListValidator : IPropertyValidator @@ -39,7 +126,7 @@ namespace Umbraco.Web.PropertyEditors { var json = value as JArray; if (json == null) yield break; - + //validate each item which is a json object for (var index = 0; index < json.Count; index++) {