more work with saving, publishing and validation for variants, fixes up a few old things too

This commit is contained in:
Shannon
2018-08-14 23:19:32 +10:00
parent 9c84d5e3f9
commit 66a00546d1
8 changed files with 140 additions and 161 deletions

View File

@@ -232,7 +232,7 @@ namespace Umbraco.Core.Migrations.Install
private void CreateLanguageData()
{
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" });
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)" });
}
private void CreateContentChildTypeData()

View File

@@ -27,7 +27,11 @@ namespace Umbraco.Core.Persistence.Factories
if (xdtos.TryGetValue(propertyType.Id, out var propDtos))
{
foreach (var propDto in propDtos)
{
property.Id = propDto.Id;
property.FactorySetValue(languageRepository.GetIsoCodeById(propDto.LanguageId), propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value);
}
}
property.ResetDirtyProperties(false);

View File

@@ -306,7 +306,8 @@
saveMethod: args.saveMethod,
scope: $scope,
content: $scope.content,
action: args.action
action: args.action,
showNotifications: args.showNotifications
}).then(function (data) {
//success
init($scope.content);
@@ -441,7 +442,8 @@
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: contentResource.publish,
action: "publish"
action: "publish",
showNotifications: false
}).then(function (data) {
overlayService.close();
return $q.when(data);
@@ -451,21 +453,6 @@
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//check the error list for specific variant errors, if none exist that means that only server side validation
//for the current variant's properties failed, in this case we want to close the publish dialog since the user
//will need to fix validation errors on the properties
if (err.data && err.data.ModelState) {
var keys = _.keys(err.data.ModelState);
var foundVariantError = _.find(keys,
function (k) {
return k.startsWith("_content_variant_");
});
if (!foundVariantError) {
//no variant errors, close the dialog
overlayService.close();
}
}
return $q.reject(err);
});
},
@@ -500,7 +487,8 @@
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: $scope.saveMethod(),
action: "save"
action: "save",
showNotifications: false
}).then(function (data) {
overlayService.close();
return $q.when(data);
@@ -509,22 +497,7 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//check the error list for specific variant errors, if none exist that means that only server side validation
//for the current variant's properties failed, in this case we want to close the publish dialog since the user
//will need to fix validation errors on the properties
if (err.data && err.data.ModelState) {
var keys = _.keys(err.data.ModelState);
var foundVariantError = _.find(keys,
function (k) {
return k.startsWith("_content_variant_");
});
if (!foundVariantError) {
//no variant errors, close the dialog
overlayService.close();
}
}
return $q.reject(err);
});
},

View File

@@ -7,19 +7,19 @@
**/
function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, navigationService, localizationService, serverValidationManager, dialogService, formHelper, appState) {
function isValidIdentifier(id){
function isValidIdentifier(id) {
//empty id <= 0
if(angular.isNumber(id) && id > 0){
if (angular.isNumber(id) && id > 0) {
return true;
}
//empty guid
if(id === "00000000-0000-0000-0000-000000000000"){
if (id === "00000000-0000-0000-0000-000000000000") {
return false;
}
//empty string / alias
if(id === ""){
if (id === "") {
return false;
}
@@ -44,6 +44,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
if (!args.saveMethod) {
throw "args.saveMethod is not defined";
}
if (args.showNotifications === undefined) {
args.showNotifications = true;
}
var redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true;
var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true;
@@ -66,7 +69,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
scope: args.scope,
savedContent: data,
redirectOnSuccess: redirectOnSuccess,
rebindCallback: function() {
rebindCallback: function () {
rebindCallback.apply(self, [args.content, data]);
}
});
@@ -76,13 +79,14 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
}, function (err) {
self.handleSaveError({
showNotifications: args.showNotifications,
redirectOnFailure: redirectOnFailure,
err: err,
rebindCallback: function() {
rebindCallback: function () {
rebindCallback.apply(self, [args.content, err.data]);
}
});
args.scope.busy = false;
return $q.reject(err);
});
@@ -90,9 +94,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
else {
return $q.reject();
}
},
/** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */
addInfoTab: function (tabs) {
@@ -165,7 +169,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
handler: args.methods.sendToPublish,
hotKey: "ctrl+p",
hotKeyWhenHidden: true,
alias: "sendToPublish"
alias: "sendToPublish"
};
case "A":
//save
@@ -175,7 +179,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
handler: args.methods.save,
hotKey: "ctrl+s",
hotKeyWhenHidden: true,
alias: "save"
alias: "save"
};
case "Z":
//unpublish
@@ -185,7 +189,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
handler: args.methods.unPublish,
hotKey: "ctrl+u",
hotKeyWhenHidden: true,
alias: "unpublish"
alias: "unpublish"
};
default:
return null;
@@ -247,15 +251,15 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// If we have a scheduled publish or unpublish date change the default button to
// "save" and update the label to "save and schedule
if(args.content.releaseDate || args.content.removeDate) {
if (args.content.releaseDate || args.content.removeDate) {
// if save button is alread the default don't change it just update the label
if (buttons.defaultButton && buttons.defaultButton.letter === "A") {
buttons.defaultButton.labelKey = "buttons_saveAndSchedule";
return;
}
if(buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) {
if (buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) {
// save a copy of the default so we can push it to the sub buttons later
var defaultButtonCopy = angular.copy(buttons.defaultButton);
var newSubButtons = [];
@@ -313,66 +317,66 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* @description
* Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state
*/
getAllowedActions : function(content, creating){
getAllowedActions: function (content, creating) {
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
// Publish, SendToPublish, Save
var actionOrder = ["U", "H", "A"];
var defaultActions;
var actions = [];
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
// Publish, SendToPublish, Save
var actionOrder = ["U", "H", "A"];
var defaultActions;
var actions = [];
//Create the first button (primary button)
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
if (!creating || _.contains(content.allowedActions, "C")) {
for (var b in actionOrder) {
if (_.contains(content.allowedActions, actionOrder[b])) {
defaultAction = actionOrder[b];
break;
}
//Create the first button (primary button)
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
if (!creating || _.contains(content.allowedActions, "C")) {
for (var b in actionOrder) {
if (_.contains(content.allowedActions, actionOrder[b])) {
defaultAction = actionOrder[b];
break;
}
}
}
actions.push(defaultAction);
//Now we need to make the drop down button list, this is also slightly tricky because:
//We cannot have any buttons if there's no default button above.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
if (defaultAction) {
//get the last index of the button order
var lastIndex = _.indexOf(actionOrder, defaultAction);
//add the remaining
for (var i = lastIndex + 1; i < actionOrder.length; i++) {
if (_.contains(content.allowedActions, actionOrder[i])) {
actions.push(actionOrder[i]);
}
}
actions.push(defaultAction);
//Now we need to make the drop down button list, this is also slightly tricky because:
//We cannot have any buttons if there's no default button above.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
if (defaultAction) {
//get the last index of the button order
var lastIndex = _.indexOf(actionOrder, defaultAction);
//add the remaining
for (var i = lastIndex + 1; i < actionOrder.length; i++) {
if (_.contains(content.allowedActions, actionOrder[i])) {
actions.push(actionOrder[i]);
}
}
//if we are not creating, then we should add unpublish too,
// so long as it's already published and if the user has access to publish
if (!creating) {
if (content.publishDate && _.contains(content.allowedActions,"U")) {
actions.push("Z");
}
//if we are not creating, then we should add unpublish too,
// so long as it's already published and if the user has access to publish
if (!creating) {
if (content.publishDate && _.contains(content.allowedActions, "U")) {
actions.push("Z");
}
}
}
return actions;
},
return actions;
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#getButtonFromAction
* @methodOf umbraco.services.contentEditingHelper
* @function
*
* @description
* Returns a button object to render a button for the tabbed editor
* currently only returns built in system buttons for content and media actions
* returns label, alias, action char and hot-key
*/
getButtonFromAction : function(ch){
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#getButtonFromAction
* @methodOf umbraco.services.contentEditingHelper
* @function
*
* @description
* Returns a button object to render a button for the tabbed editor
* currently only returns built in system buttons for content and media actions
* returns label, alias, action char and hot-key
*/
getButtonFromAction: function (ch) {
switch (ch) {
case "U":
return {
@@ -407,7 +411,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
return null;
}
},
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#reBindChangedProperties
@@ -421,11 +426,11 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
* This returns the list of changed content properties (does not include standard object property changes).
*/
reBindChangedProperties: function (origContent, savedContent) {
//TODO: We should probably split out this logic to deal with media/members seperately to content
//a method to ignore built-in prop changes
var shouldIgnore = function(propName) {
var shouldIgnore = function (propName) {
return _.some([
"variants",
"notifications",
@@ -465,9 +470,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//Now re-bind content properties. Since content has variants and media/members doesn't,
//we'll detect the variants property for content to distinguish if it's content vs media/members.
var isContent = false;
var origVariants = [];
var savedVariants = [];
if (origContent.variants) {
isContent = true;
//it's contnet so assign the variants as they exist
origVariants = origContent.variants;
savedVariants = savedContent.variants;
@@ -492,6 +500,12 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var origVariant = origVariants[j];
var savedVariant = savedVariants[j];
//special case for content, don't sync this variant if it wasn't tagged
//for saving in the first place
if (!savedVariant.save) {
continue;
}
//if it's content (not media/members), then we need to sync the variant specific data
if (origContent.variants) {
@@ -501,7 +515,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//loop through the properties returned on the server object
for (var b in savedVariant) {
var shouldCompare = _.some(variantPropertiesSync, function(e) {
var shouldCompare = _.some(variantPropertiesSync, function (e) {
return e === b;
});
@@ -528,7 +542,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//they have changed so set the origContent prop to the new one
var origVal = origProp.value;
origProp.value = newProp.value;
//instead of having a property editor $watch their expression to check if it has
@@ -539,7 +553,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
origProp.onValueChanged(origProp.value, origVal);
}
changed.push(origProp);
changed.push(origProp);
}
}
@@ -577,6 +591,13 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
//wire up the server validation errs
formHelper.handleServerValidation(args.err.data.ModelState);
//add model state errors to notifications
if (args.showNotifications) {
for (var e in modelState) {
notificationsService.error("Validation", modelState[e][0]);
}
}
if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) {
//we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data. Then we need
@@ -621,7 +642,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// the default behaviour is to redirect on success. This adds option to prevent when false
args.redirectOnSuccess = args.redirectOnSuccess !== undefined ? args.redirectOnSuccess : true;
if (!args.redirectOnSuccess || !this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) {
//we are not redirecting because this is not new content, it is existing content. In this case
@@ -646,9 +667,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
*/
redirectToCreatedContent: function (id, modelState) {
//only continue if we are currently in create mode and not in infinite mode and if there is no 'Name' modelstate errors
// since we need at least a name to create content.
if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) {
//only continue if we are currently in create mode and not in infinite mode and if the resulting ID is valid
if ($routeParams.create && (isValidIdentifier(id))) {
//need to change the location to not be in 'create' mode. Currently the route will be something like:
// /belle/#/content/edit/1234?doctype=newsArticle&create=true
@@ -659,7 +679,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
navigationService.clearSearch();
//change to new path
$location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id);
$location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id);
//don't add a browser history for this
$location.replace();
return true;

View File

@@ -190,9 +190,6 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati
serverValidationManager.addFieldError(e, modelState[e][0]);
}
//add to notifications
notificationsService.error("Validation", modelState[e][0]);
}
}
};

View File

@@ -4,32 +4,13 @@
function PublishController($scope, localizationService) {
var vm = this;
vm.variants = $scope.model.variants;
vm.changeSelection = changeSelection;
vm.loading = true;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
vm.hasPristineVariants = false;
//watch this model, if it's reset, then re init
$scope.$watch("model.variants",
function (newVal, oldVal) {
vm.variants = newVal;
if (oldVal && oldVal.length) {
//re-bind the selections
for (var i = 0; i < oldVal.length; i++) {
var found = _.find(vm.variants, function (v) {
return v.language.id === oldVal[i].language.id;
});
if (found) {
found.publish = oldVal[i].publish;
}
}
}
onInit();
});
vm.changeSelection = changeSelection;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
/** Returns true if publishing is possible based on if there are un-published mandatory languages */
function canPublish() {
var selected = [];
@@ -57,6 +38,8 @@
function changeSelection(variant) {
$scope.model.disableSubmitButton = !canPublish();
//need to set the Save state to true if publish is true
variant.save = variant.publish;
}
function dirtyVariantFilter(variant) {
@@ -75,6 +58,8 @@
function onInit() {
vm.variants = $scope.model.variants;
if (!$scope.model.title) {
localizationService.localize("content_readyToPublish").then(function (value) {
$scope.model.title = value;
@@ -107,6 +92,7 @@
if (active) {
//ensure that the current one is selected
active.publish = true;
active.save = true;
}
$scope.model.disableSubmitButton = !canPublish();
@@ -120,10 +106,13 @@
}
onInit();
//when this dialog is closed, reset all 'publish' flags
$scope.$on('$destroy', function () {
for (var i = 0; i < vm.variants.length; i++) {
vm.variants[i].publish = false;
vm.variants[i].save = false;
}
});
}

View File

@@ -4,31 +4,13 @@
function SaveContentController($scope, localizationService) {
var vm = this;
vm.variants = $scope.model.variants;
vm.changeSelection = changeSelection;
vm.loading = true;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
vm.hasPristineVariants = false;
//watch this model, if it's reset, then re init
$scope.$watch("model.variants",
function (newVal, oldVal) {
vm.variants = newVal;
if (oldVal && oldVal.length) {
//re-bind the selections
for (var i = 0; i < oldVal.length; i++) {
var found = _.find(vm.variants, function (v) {
return v.language.id === oldVal[i].language.id;
});
if (found) {
found.save = oldVal[i].save;
}
}
}
onInit();
});
vm.changeSelection = changeSelection;
vm.dirtyVariantFilter = dirtyVariantFilter;
vm.pristineVariantFilter = pristineVariantFilter;
function changeSelection(variant) {
var firstSelected = _.find(vm.variants, function (v) {
return v.save;
@@ -50,6 +32,8 @@
function onInit() {
vm.variants = $scope.model.variants;
if(!$scope.model.title) {
localizationService.localize("content_readyToSave").then(function(value){
$scope.model.title = value;
@@ -92,6 +76,8 @@
vm.loading = false;
}
onInit();
//when this dialog is closed, reset all 'save' flags
$scope.$on('$destroy', function () {
for (var i = 0; i < vm.variants.length; i++) {

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
@@ -148,7 +149,16 @@ namespace Umbraco.Web.Editors.Filters
// validate
var valueEditor = editor.GetValueEditor(p.DataType.Configuration);
foreach (var r in valueEditor.Validate(postedValue, p.IsRequired, p.ValidationRegExp))
{
//this could be a thing, but it does make the errors seem very verbose
////update the error message to include the property name and culture if available
//r.ErrorMessage = p.Culture.IsNullOrWhiteSpace()
// ? $"'{p.Label}' - {r.ErrorMessage}"
// : $"'{p.Label}' ({p.Culture}) - {r.ErrorMessage}";
modelState.AddPropertyError(r, p.Alias, p.Culture);
}
}
return modelState.IsValid;