Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7

This commit is contained in:
Stephan
2017-10-17 15:00:13 +02:00
23 changed files with 480 additions and 112 deletions

View File

@@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors
return result;
}
private void ConvertItemsToJsonIfDetected(IDictionary<string, object> result)
protected void ConvertItemsToJsonIfDetected(IDictionary<string, object> 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
}
}
}

View File

@@ -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<JObject>(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;
}
}
}
}

View File

@@ -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" },

View File

@@ -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;

View File

@@ -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("<a data-ng-click=\"tgl()\">Toggle</a>");
$compile(lnk)(scope);
elem.wrap("<div class=\"password-toggle\"/>").after(lnk);
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective);
})();

View File

@@ -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");
}
}

View File

@@ -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;
}

View File

@@ -24,8 +24,8 @@
</label>
<input type="password" ng-model="invitedUserPasswordModel.password" name="password" class="-full-width-input" umb-auto-focus required val-server-field="value" autocomplete="off" ng-minlength="{{invitedUserPasswordModel.passwordPolicies.minPasswordLength}}" />
<span class="help-inline" val-msg-for="password" val-toggle-msg="required"><localize key="user_passwordIsBlank">Your new password cannot be blank!</localize></span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters</span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServerField"></span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters</span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServerField"></span>
</div>
<div class="control-group" ng-class="{error: setPasswordForm.confirmPassword.$invalid}">
@@ -49,15 +49,15 @@
<div class="form" ng-if="inviteStep === 2">
<div class="flex justify-center items-center">
<ng-form name="avatarForm">
<umb-progress-bar style="max-width: 100px; margin-bottom: 5px;"
ng-show="avatarFile.uploadStatus === 'uploading'"
progress="{{ avatarFile.uploadProgress }}"
size="s">
</umb-progress-bar>
<div class="umb-info-local-item text-error mt3" ng-if="avatarFile.uploadStatus === 'error'">
{{ avatarFile.serverErrorMessage }}
</div>
@@ -69,7 +69,7 @@
ngf-multiple="false"
ngf-pattern="{{avatarFile.acceptedFileTypes}}"
ngf-max-size="{{ avatarFile.maxFileSize }}">
<umb-avatar color="gray"
size="xl"
unknown-char="+"
@@ -77,7 +77,7 @@
img-srcset="{{invitedUser.avatars[4]}} 2x, {{invitedUser.avatars[4]}} 3x">
</umb-avatar>
</a>
</ng-form>
</div>
@@ -149,7 +149,7 @@
<div class="control-group" ng-class="{error: loginForm.password.$invalid}">
<label><localize key="general_password">Password</localize></label>
<input type="password" ng-model="password" name="password" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" autocomplete="off" />
<input type="password" ng-model="password" name="password" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" autocomplete="off" umb-password-toggle />
</div>
<div class="flex justify-between items-center">

View File

@@ -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);

View File

@@ -5,10 +5,10 @@
</div>
<ul class="thumbnails color-picker">
<li ng-repeat="(key, val) in model.config.items" ng-class="{active: model.value === val}">
<a ng-click="toggleItem(val)" class="thumbnail" hex-bg-color="{{val}}">
</a>
<li ng-repeat="(key, val) in model.config.items" ng-class="{active: isActiveColor(val)}">
<a ng-click="toggleItem(val)" class="thumbnail" hex-bg-color="{{val.value}}">
</a>
<span class="color-label" ng-if="model.useLabel" ng-bind="val.label"></span>
</li>
</ul>

View File

@@ -1,12 +1,13 @@
<div ng-controller="Umbraco.PrevalueEditors.MultiColorPickerController">
<div class="control-group color-picker-preval">
<input name="newColor" type="hidden" />
<label for="newColor" val-highlight="hasError">#{{newColor}}</label>
<input name="newLabel" type="text" ng-model="newLabel" class="umb-editor color-label" placeholder="Label" />
<button class="btn add" ng-click="add($event)"><localize key="general_add">Add</localize></button>
<label for="newColor" val-highlight="hasError">{{newColor}}</label>
</div>
<div class="control-group color-picker-preval" ng-repeat="item in model.value">
<div class="thumbnail span1" hex-bg-color="{{item.value}}" bg-orig="transparent"></div>
<pre>{{item.value}}</pre>
<div class="color-picker-prediv"><pre>#{{item.value}} - {{item.label}}</pre></div>
<button class="btn btn-danger" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></button>
</div>
</div>

View File

@@ -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;
}

View File

@@ -48,14 +48,18 @@
<a href ng-click="scaleDown(currentCell)">
<i class="icon icon-remove"></i>
</a>
{{currentCell.grid}}
{{currentCell.grid}}
<a href ng-click="scaleUp(currentCell, availableRowSpace, true)">
<i class="icon icon-add"></i>
</a>
</div>
</umb-control-group>
<umb-control-group label="@grid_maxItems" description="@grid_maxItemsDescription">
<input type="number" ng-model="currentCell.maxItems" class="umb-editor-tiny" placeholder="Max" min="0" />
</umb-control-group>
<umb-control-group hide-label="true">
<umb-control-group hide-label="true">
<i class="icon-delete red"></i>
<a class="btn btn-small btn-link" href="" ng-click="deleteArea(currentCell, currentRow)">
<localize key="general_delete" class="ng-isolate-scope ng-scope">Delete</localize>

View File

@@ -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";

View File

@@ -224,7 +224,7 @@
<!-- Controls repeat end -->
<!-- if area is empty tools -->
<div class="umb-grid-add-more-content" ng-if="area.controls.length > 0 && !sortMode">
<div class="umb-grid-add-more-content" ng-if="area.controls.length > 0 && !sortMode && (area.maxItems == undefined || area.maxItems == '' || area.maxItems == 0 || area.maxItems > area.controls.length)">
<div class="cell-tools-add -bar newbtn" ng-click="openEditorOverlay($event, area, 0, area.$uniqueId);"><localize key="grid_addElement" /></div>
</div>

View File

@@ -65,7 +65,9 @@
ng-repeat="area in layout.areas | filter:zeroWidthFilter"
ng-style="{width: percentage(area.grid) + '%', 'max-width': '100%'}">
<div class="preview-cell"></div>
<div class="preview-cell">
<p style="font-size: 6px; line-height: 8px; text-align: center" ng-show="area.maxItems > 0">{{area.maxItems}}</p>
</div>
</div>
</div>
</div>

View File

@@ -1260,6 +1260,9 @@ Mange hilsner fra Umbraco robotten
<key alias="chooseDefault">Vælg standard</key>
<key alias="areAdded">er tilføjet</key>
<key alias="maxItems">Maksimalt emner</key>
<key alias="maxItemsDescription">Efterlad blank eller sat til 0 ubegrænset for</key>
</area>
<area alias="contentTypeEditor">

View File

@@ -1419,6 +1419,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="allowAllEditors">Allow all editors</key>
<key alias="allowAllRowConfigurations">Allow all row configurations</key>
<key alias="maxItemsDescription">Leave blank or set to 0 for unlimited</key>
<key alias="maxItems">Maximum items</key>
<key alias="setAsDefault">Set as default</key>
<key alias="chooseExtra">Choose extra</key>
<key alias="chooseDefault">Choose default</key>

View File

@@ -1406,6 +1406,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="allowAllEditors">Allow all editors</key>
<key alias="allowAllRowConfigurations">Allow all row configurations</key>
<key alias="maxItems">Maximum items</key>
<key alias="maxItemsDescription">Leave blank or set to 0 for unlimited</key>
<key alias="setAsDefault">Set as default</key>
<key alias="chooseExtra">Choose extra</key>
<key alias="chooseDefault">Choose default</key>

View File

@@ -789,6 +789,9 @@
<key alias="allowAllEditors">Permitir todos los controles de edición</key>
<key alias="allowAllRowConfigurations">Permitir todas las configuraciones de fila</key>
<key alias="maxItems">Artículos máximos</key>
<key alias="maxItemsDescription">Laat dit leeg of is ingesteld op -1 voor onbeperkt</key>
<key alias="maxItemsDescription">Dejar en blanco o se establece en 0 para ilimitada</key>
</area>
<area alias="templateEditor">
<key alias="alternativeField">Campo opcional</key>

View File

@@ -1077,7 +1077,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
<key alias="allowAllEditors">Alle editors toelaten</key>
<key alias="allowAllRowConfigurations">Alle rijconfiguraties toelaten</key>
<key alias="setAsDefault">Instellen als standaard</key>
<key alias="maxItems">Maximale artikelen</key>
<key alias="maxItemsDescription">Laat dit leeg of is ingesteld op -1 voor onbeperkt</key>
<key alias="setAsDefault">Instellen als standaard</key>
<key alias="chooseExtra">Kies extra</key>
<key alias="chooseDefault">Kies standaard</key>
<key alias="areAdded">zijn toegevoegd</key>

View File

@@ -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
}
}
/// <summary>
/// Checks if the user is currently in preview mode and if so will update the preview content for this item
/// </summary>
/// <param name="contentId"></param>
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();
}
}
/// <summary>
/// Maps the dto property values to the persisted model
/// </summary>

View File

@@ -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<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
{
var dictionary = persistedPreVals.FormatAsDictionary();
var arrayOfVals = dictionary.Select(item => item.Value).ToList();
return new Dictionary<string, object> { { "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<int, object>();
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<string, object> { { "items", items2 } };
var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1";
if (useLabel)
result["useLabel"] = dictionary["useLabel"].Value;
return result;
}
public override IDictionary<string, PreValue> ConvertEditorToDb(IDictionary<string, object> editorValue, PreValueCollection currentValue)
{
var val = editorValue["items"] as JArray;
var result = new Dictionary<string, PreValue>();
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<JObject>()
.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<ValueListPreValueEditor>("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++)
{