diff --git a/src/Umbraco.Web.UI.Client/.jshintignore b/src/Umbraco.Web.UI.Client/.jshintignore deleted file mode 100644 index 97620d1820..0000000000 --- a/src/Umbraco.Web.UI.Client/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -src/common/services/util.service.js \ No newline at end of file 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 264c8f140d..0586651842 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 @@ -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); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 5fb6fe1625..e879c3aca0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -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); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 9fe3324a8f..b5251cfeca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -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", diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 228c885529..1619ca0623 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -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); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index b49046feac..78463fcee0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -27,7 +27,7 @@ * -
+
@@ -35,6 +35,10 @@
{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}
+
+
{{notification.message}}
+
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html index dbe4c8fe24..05c6f00fa2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html @@ -27,14 +27,18 @@ * -
+
{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}
- + +
+
{{notification.message}}
+
+
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 9d98dce3af..b233980507 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1396,15 +1396,15 @@ To manage your website, simply open the Umbraco back office and start adding con Datatype saved Dictionary item saved Publishing failed because the parent page isn't published - %0% variant published Content published - and visible on the website - %0% variant saved + and is visible on the website + %0% published and visible on the website Content saved Remember to publish to make changes visible + %0% saved Sent For Approval Changes have been sent for approval - %0% variant changes have been sent for approval + %0% changes have been sent for approval Media saved Media saved without any errors Member saved diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 5041c411df..191acfd96f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -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 + { + //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; } + /// + /// Used to add success notifications globally and for the culture + /// + /// + /// + /// + /// + /// + /// 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. + /// + private static void AddSuccessNotification(IDictionary 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); + } + /// /// Performs the publishing operation for a content item /// @@ -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; diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs index 3078fe97da..90da6d6d04 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariationDisplay.cs @@ -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 /// [DataContract(Name = "contentVariant", Namespace = "")] - public class ContentVariantDisplay : ITabbedContent, IContentProperties + public class ContentVariantDisplay : ITabbedContent, IContentProperties, INotificationModel { public ContentVariantDisplay() { Tabs = new List>(); + Notifications = new List(); } [DataMember(Name = "name", IsRequired = true)] @@ -59,6 +61,16 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "publishDate")] public DateTime? PublishDate { get; set; } - + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + /// + /// The notifications assigned to a variant are currently only used to show custom messagse in the save/publish dialogs. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/Notification.cs b/src/Umbraco.Web/Models/ContentEditing/Notification.cs index 495b80eb83..1ed20d27d4 100644 --- a/src/Umbraco.Web/Models/ContentEditing/Notification.cs +++ b/src/Umbraco.Web/Models/ContentEditing/Notification.cs @@ -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; } + } }