Gets success notifications showing in the save/publish dialogs

This commit is contained in:
Shannon
2018-09-04 16:16:11 +10:00
parent f9657ae04d
commit 3f4e6dfce8
11 changed files with 139 additions and 46 deletions

View File

@@ -1 +0,0 @@
src/common/services/util.service.js

View File

@@ -252,6 +252,19 @@
});
}
function clearNotifications(content) {
if (content.notifications) {
content.notifications = [];
}
if (content.variants) {
for (var i = 0; i < content.variants.length; i++) {
if (content.variants[i].notifications) {
content.variants[i].notifications = [];
}
}
}
}
function resetLastListPageNumber(content) {
// We're using rootScope to store the page number for list views, so if returning to the list
// we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children
@@ -341,7 +354,7 @@
};
$scope.saveAndPublish = function () {
clearNotifications($scope.content);
// TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (showSaveOrPublishDialog()) {
//before we launch the dialog we want to execute all client side validations first
@@ -355,13 +368,16 @@
submitButtonLabel: "Publish",
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: contentResource.publish,
action: "publish",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
@@ -369,7 +385,6 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
@@ -390,7 +405,7 @@
};
$scope.save = function () {
clearNotifications($scope.content);
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
if (showSaveOrPublishDialog()) {
//before we launch the dialog we want to execute all client side validations first
@@ -404,13 +419,16 @@
submitButtonLabel: "Save",
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
return performSave({
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
@@ -418,7 +436,6 @@
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});

View File

@@ -26,12 +26,14 @@
function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
/** internal method process the saving of data and post processing the result */
function saveContentItem(content, action, files, restApiUrl) {
function saveContentItem(content, action, files, restApiUrl, showNotifications) {
return umbRequestHelper.postSaveContent({
restApiUrl: restApiUrl,
content: content,
action: action,
files: files,
showNotifications: showNotifications,
dataFormatter: function (c, a) {
return umbDataFormatter.formatContentPostData(c, a);
}
@@ -632,22 +634,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
* @param {Object} content The content item object with changes applied
* @param {Bool} isNew set to true to create a new item or to update an existing
* @param {Array} files collection of files for the document
* @param {Array} files collection of files for the document
* @param {Bool} showNotifications an option to disable/show notifications (default is true)
* @returns {Promise} resourcePromise object containing the saved content item.
*
*/
save: function (content, isNew, files) {
save: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSave");
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},
saveBlueprint: function (content, isNew, files) {
saveBlueprint: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSaveBlueprint");
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},
/**
@@ -674,15 +677,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
* @param {Object} content The content item object with changes applied
* @param {Bool} isNew set to true to create a new item or to update an existing
* @param {Array} files collection of files for the document
* @param {Array} files collection of files for the document
* @param {Bool} showNotifications an option to disable/show notifications (default is true)
* @returns {Promise} resourcePromise object containing the saved content item.
*
*/
publish: function (content, isNew, files) {
publish: function (content, isNew, files, showNotifications) {
var endpoint = umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostSave");
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint);
return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint, showNotifications);
},

View File

@@ -66,7 +66,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
args.scope.busy = true;
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
return args.saveMethod(args.content, $routeParams.create, fileManager.getFiles(), args.showNotifications)
.then(function (data) {
formHelper.resetForm({ scope: args.scope });
@@ -439,8 +439,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
var shouldIgnore = function (propName) {
return _.some([
"variants",
"notifications",
"ModelState",
"tabs",
"properties",
"apps",

View File

@@ -136,8 +136,8 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
//create the callbacs based on whats been passed in.
var callbacks = {
success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
error: ((!opts || !opts.error) ? defaultError : opts.error)
success: (!opts || !opts.success) ? defaultSuccess : opts.success,
error: (!opts || !opts.error ? defaultError : opts.error)
};
return httpPromise.then(function (response) {
@@ -156,7 +156,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
//this is a JS/angular error that we should deal with
return $q.reject({
errorMsg: response.message
})
});
}
//invoke the callback
@@ -188,12 +188,22 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
errorMsg: result.errorMsg,
data: result.data,
status: result.status
})
});
});
},
/** Used for saving content/media/members specifically */
/**
* @ngdoc method
* @name umbraco.resources.contentResource#postSaveContent
* @methodOf umbraco.resources.contentResource
*
* @description
* Used for saving content/media/members specifically
*
* @param {Object} args arguments object
* @returns {Promise} http promise object.
*/
postSaveContent: function (args) {
if (!args.restApiUrl) {
@@ -211,6 +221,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
if (!args.dataFormatter) {
throw "args.dataFormatter is a required argument";
}
if (args.showNotifications === null || args.showNotifications === undefined) {
args.showNotifications = true;
}
//save the active tab id so we can set it when the data is returned.
var activeTab = _.find(args.content.tabs, function (item) {
@@ -246,7 +259,9 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
response.data.tabs[activeTabIndex].active = true;
}
formHelper.showNotifications(response.data);
if (args.showNotifications) {
formHelper.showNotifications(response.data);
}
//TODO: Do we need to pass the result through umbDataFormatter.formatContentGetData? Right now things work so not sure but we should check
@@ -278,7 +293,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
}
}
else {
else if (args.showNotifications) {
formHelper.showNotifications(response.data);
}

View File

@@ -27,7 +27,7 @@
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</label>
<div ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid">
<div ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
@@ -35,6 +35,10 @@
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>

View File

@@ -27,14 +27,18 @@
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</label>
<div ng-if="!saveVariantSelectorForm.saveVariantSelector.$invalid">
<div ng-if="!saveVariantSelectorForm.saveVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
<div ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<div class="umb-permission__description" style="color: #F02E28;" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</div>
</div>
<div ng-repeat="notification in variant.notifications">
<div class="umb-permission__description" style="color: #1FB572;">{{notification.message}}</div>
</div>
</div>
</div>

View File

@@ -1396,15 +1396,15 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="dataTypeSaved">Datatype saved</key>
<key alias="dictionaryItemSaved">Dictionary item saved</key>
<key alias="editContentPublishedFailedByParent">Publishing failed because the parent page isn't published</key>
<key alias="editVariantContentPublishedHeader">%0% variant published</key>
<key alias="editContentPublishedHeader">Content published</key>
<key alias="editContentPublishedText">and visible on the website</key>
<key alias="editVariantSavedHeader">%0% variant saved</key>
<key alias="editContentPublishedText">and is visible on the website</key>
<key alias="editVariantPublishedText">%0% published and visible on the website</key>
<key alias="editContentSavedHeader">Content saved</key>
<key alias="editContentSavedText">Remember to publish to make changes visible</key>
<key alias="editVariantSavedText">%0% saved</key>
<key alias="editContentSendToPublish">Sent For Approval</key>
<key alias="editContentSendToPublishText">Changes have been sent for approval</key>
<key alias="editVariantSendToPublishText">%0% variant changes have been sent for approval</key>
<key alias="editVariantSendToPublishText">%0% changes have been sent for approval</key>
<key alias="editMediaSaved">Media saved</key>
<key alias="editMediaSavedText">Media saved without any errors</key>
<key alias="editMemberSaved">Member saved</key>

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.ValueProviders;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -644,7 +645,12 @@ namespace Umbraco.Web.Editors
bool wasCancelled;
//used to track successful notifications
var notifications = new SimpleNotificationModel();
var globalNotifications = new SimpleNotificationModel();
var notifications = new Dictionary<string, SimpleNotificationModel>
{
//global (non variant specific) notifications
[string.Empty] = globalNotifications
};
switch (contentItem.Action)
{
@@ -659,14 +665,14 @@ namespace Umbraco.Web.Editors
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
{
notifications.AddSuccessNotification(
Services.TextService.Localize("speechBubbles/editVariantSavedHeader", new[] {_allLangs.Value[c].CultureName}),
Services.TextService.Localize("speechBubbles/editContentSavedText"));
AddSuccessNotification(notifications, c,
Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
Services.TextService.Localize("speechBubbles/editVariantSavedText", new[] {_allLangs.Value[c].CultureName}));
}
}
else if (ModelState.IsValid)
{
notifications.AddSuccessNotification(
globalNotifications.AddSuccessNotification(
Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
Services.TextService.Localize("speechBubbles/editContentSavedText"));
}
@@ -683,14 +689,14 @@ namespace Umbraco.Web.Editors
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
{
notifications.AddSuccessNotification(
AddSuccessNotification(notifications, c,
Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
Services.TextService.Localize("speechBubbles/editVariantSendToPublishText", new[] { _allLangs.Value[c].CultureName }));
}
}
else if (ModelState.IsValid)
{
notifications.AddSuccessNotification(
globalNotifications.AddSuccessNotification(
Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
}
@@ -699,7 +705,13 @@ namespace Umbraco.Web.Editors
case ContentSaveAction.Publish:
case ContentSaveAction.PublishNew:
PublishInternal(contentItem, ref publishStatus, out wasCancelled, out var successfulCultures);
AddMessageForPublishStatus(publishStatus, notifications, successfulCultures);
//global notifications
AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures);
//variant specific notifications
foreach (var c in successfulCultures)
{
AddMessageForPublishStatus(publishStatus, notifications.GetOrCreate(c), successfulCultures);
}
break;
default:
throw new ArgumentOutOfRangeException();
@@ -709,7 +721,12 @@ namespace Umbraco.Web.Editors
var display = MapToDisplay(contentItem.PersistedContent);
//merge the tracked success messages with the outgoing model
display.Notifications.AddRange(notifications.Notifications);
display.Notifications.AddRange(globalNotifications.Notifications);
foreach (var v in display.Variants)
{
if (notifications.TryGetValue(v.Language.IsoCode, out var n))
v.Notifications.AddRange(n.Notifications);
}
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -731,6 +748,25 @@ namespace Umbraco.Web.Editors
return display;
}
/// <summary>
/// Used to add success notifications globally and for the culture
/// </summary>
/// <param name="notifications"></param>
/// <param name="culture"></param>
/// <param name="header"></param>
/// <param name="msg"></param>
/// <remarks>
/// global notifications will be shown if all variant processing is successful and the save/publish dialog is closed, otherwise
/// variant specific notifications are used to show success messagse in the save/publish dialog.
/// </remarks>
private static void AddSuccessNotification(IDictionary<string, SimpleNotificationModel> notifications, string culture, string header, string msg)
{
//add the global notification (which will display globally if all variants are successfully processed)
notifications[string.Empty].AddSuccessNotification(header, msg);
//add the variant specific notification (which will display in the dialog if all variants are not successfully processed)
notifications.GetOrCreate(culture).AddSuccessNotification(header, msg);
}
/// <summary>
/// Performs the publishing operation for a content item
/// </summary>
@@ -1412,8 +1448,8 @@ namespace Umbraco.Web.Editors
foreach (var c in successfulCultures)
{
display.AddSuccessNotification(
Services.TextService.Localize("speechBubbles/editVariantContentPublishedHeader", new[]{ _allLangs.Value[c].CultureName}),
Services.TextService.Localize("speechBubbles/editContentPublishedText"));
Services.TextService.Localize("speechBubbles/editContentPublishedHeader"),
Services.TextService.Localize("speechBubbles/editVariantPublishedText", new[] { _allLangs.Value[c].CultureName }));
}
}
break;

View File

@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
@@ -12,11 +13,12 @@ namespace Umbraco.Web.Models.ContentEditing
/// Represents the variant info for a content item
/// </summary>
[DataContract(Name = "contentVariant", Namespace = "")]
public class ContentVariantDisplay : ITabbedContent<ContentPropertyDisplay>, IContentProperties<ContentPropertyDisplay>
public class ContentVariantDisplay : ITabbedContent<ContentPropertyDisplay>, IContentProperties<ContentPropertyDisplay>, INotificationModel
{
public ContentVariantDisplay()
{
Tabs = new List<Tab<ContentPropertyDisplay>>();
Notifications = new List<Notification>();
}
[DataMember(Name = "name", IsRequired = true)]
@@ -59,6 +61,16 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "publishDate")]
public DateTime? PublishDate { get; set; }
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>
/// <remarks>
/// The notifications assigned to a variant are currently only used to show custom messagse in the save/publish dialogs.
/// </remarks>
[DataMember(Name = "notifications")]
[ReadOnly(true)]
public List<Notification> Notifications { get; private set; }
}
}

View File

@@ -20,9 +20,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "header")]
public string Header { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
[DataMember(Name = "type")]
public SpeechBubbleIcon NotificationType { get; set; }
}
}