From 4cc469250dd960b6f769cda5634b9d4724ae8876 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Wed, 27 Mar 2024 13:45:00 -0700 Subject: [PATCH 1/6] Add HTML support to notification summary --- .../UpdateContentTask.Fields.Design.cshtml | 2 +- .../Activities/NotifyUserTaskActivity.cs | 9 ++- .../Assets/js/notification-manager.js | 80 ++++++++++--------- .../Controllers/AdminController.cs | 4 +- .../NotifyContentOwnerTaskDisplayDriver.cs | 6 +- .../NotifyUserTaskActivityDisplayDriver.cs | 67 +++++++++++++++- .../Drivers/NotifyUserTaskDisplayDriver.cs | 5 ++ .../Handlers/CoreNotificationEventsHandler.cs | 2 +- .../OrchardCore.Notifications.csproj | 4 + .../NotifyUserTaskActivityViewModel.cs | 2 + .../NotifyContentOwnerTask.Fields.Edit.cshtml | 9 ++- .../NotifyUserTaskActivity.Fields.Edit.cshtml | 9 ++- .../Views/Notification.Header.cshtml | 6 +- .../Views/Notification.SummaryAdmin.cshtml | 14 ++-- .../Views/UserNotificationNavbar.cshtml | 5 +- .../wwwroot/Scripts/notification-manager.js | 77 +++++++++--------- .../Scripts/notification-manager.min.js | 2 +- .../INotificationMessage.cs | 11 ++- .../Notification.cs | 5 ++ .../Models/NotificationBodyInfo.cs | 7 +- .../Models/NotificationMessage.cs | 2 + .../Services/EmailNotificationProvider.cs | 4 +- .../Services/NotificationService.cs | 1 + .../Services/SmsNotificationProvider.cs | 2 +- 24 files changed, 220 insertions(+), 115 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Views/Items/UpdateContentTask.Fields.Design.cshtml b/src/OrchardCore.Modules/OrchardCore.Contents/Views/Items/UpdateContentTask.Fields.Design.cshtml index 7647969ca50..f5ca078f340 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Views/Items/UpdateContentTask.Fields.Design.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Views/Items/UpdateContentTask.Fields.Design.cshtml @@ -1,7 +1,7 @@ @model ContentTaskViewModel
-

@Model.Activity.GetTitleOrDefault(() => T["Update Content"])

+

@Model.Activity.GetTitleOrDefault(() => T["Update Content"])

@if (string.IsNullOrWhiteSpace(Model.Activity.Content?.Expression)) diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Activities/NotifyUserTaskActivity.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Activities/NotifyUserTaskActivity.cs index 3cdc7730e94..34815c350ab 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Activities/NotifyUserTaskActivity.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Activities/NotifyUserTaskActivity.cs @@ -44,6 +44,12 @@ public WorkflowExpression Subject set => SetProperty(value); } + public WorkflowExpression Summary + { + get => GetProperty(() => new WorkflowExpression()); + set => SetProperty(value); + } + public WorkflowExpression TextBody { get => GetProperty(() => new WorkflowExpression()); @@ -97,7 +103,8 @@ protected virtual async Task GetMessageAsync(WorkflowExecu { return new NotificationMessage() { - Summary = await _expressionEvaluator.EvaluateAsync(Subject, workflowContext, null), + Subject = await _expressionEvaluator.EvaluateAsync(Subject, workflowContext, null), + Summary = await _expressionEvaluator.EvaluateAsync(Summary, workflowContext, _htmlEncoder), TextBody = await _expressionEvaluator.EvaluateAsync(TextBody, workflowContext, null), HtmlBody = await _expressionEvaluator.EvaluateAsync(HtmlBody, workflowContext, _htmlEncoder), IsHtmlPreferred = IsHtmlPreferred, diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Assets/js/notification-manager.js b/src/OrchardCore.Modules/OrchardCore.Notifications/Assets/js/notification-manager.js index 6ad0824c9b5..8dc734e8b40 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Assets/js/notification-manager.js +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Assets/js/notification-manager.js @@ -11,49 +11,51 @@ notificationManager = function () { var elements = document.getElementsByClassName('mark-notification-as-read'); for (let i = 0; i < elements.length; i++) { - let element = elements[i]; - element.addEventListener('click', () => { - - if (element.getAttribute('data-is-read') != "false") { - return; - } - - var messageId = element.getAttribute('data-message-id'); - - if (!messageId) { - return; - } - - fetch(readUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ messageId: messageId }) - }).then(response => response.json()) - .then(result => { - if (result.updated) { - if (wrapperSelector) { - var wrapper = element.closest(wrapperSelector); - if (wrapper) { - wrapper.classList.remove('notification-is-unread'); - wrapper.classList.add('notification-is-read'); - wrapper.setAttribute('data-is-read', true); + + ['click', 'mouseover'].forEach((evt) => { + elements[i].addEventListener(evt, (e) => { + + if (e.target.getAttribute('data-is-read') != "false") { + return; + } + + var messageId = e.target.getAttribute('data-message-id'); + + if (!messageId) { + return; + } + + fetch(readUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ messageId: messageId }) + }).then(response => response.json()) + .then(result => { + if (result.updated) { + if (wrapperSelector) { + var wrapper = e.target.closest(wrapperSelector); + if (wrapper) { + wrapper.classList.remove('notification-is-unread'); + wrapper.classList.add('notification-is-read'); + wrapper.setAttribute('data-is-read', true); + } + } else { + e.target.classList.remove('notification-is-unread'); + e.target.classList.add('notification-is-read'); + e.target.setAttribute('data-is-read', true); } - } else { - element.classList.remove('notification-is-unread'); - element.classList.add('notification-is-read'); - element.setAttribute('data-is-read', true); } - } - var targetUrl = element.getAttribute('data-target-url'); + var targetUrl = e.target.getAttribute('data-target-url'); - if (targetUrl) { - window.location.href = targetUrl; - } - }); - }); + if (targetUrl) { + window.location.href = targetUrl; + } + }); + }); + }) } } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Controllers/AdminController.cs index 4f3e00846a2..afbc25825f7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Controllers/AdminController.cs @@ -118,8 +118,8 @@ public async Task List( foreach (var notification in queryResult.Notifications) { - dynamic shape = await _notificationDisplayManager.BuildDisplayAsync(notification, this, "SummaryAdmin"); - shape.Notification = notification; + var shape = await _notificationDisplayManager.BuildDisplayAsync(notification, this, "SummaryAdmin"); + shape.Properties.TryAdd(nameof(Notification), notification); notificationSummaries.Add(shape); } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyContentOwnerTaskDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyContentOwnerTaskDisplayDriver.cs index e0803912ae4..1fa3d1106af 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyContentOwnerTaskDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyContentOwnerTaskDisplayDriver.cs @@ -1,8 +1,12 @@ +using System; using OrchardCore.Notifications.Activities; namespace OrchardCore.Notifications.Drivers; public class NotifyContentOwnerTaskDisplayDriver : NotifyUserTaskActivityDisplayDriver { - + public NotifyContentOwnerTaskDisplayDriver(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskActivityDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskActivityDisplayDriver.cs index 9dcd4a24af4..16ecc940a8b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskActivityDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskActivityDisplayDriver.cs @@ -1,7 +1,12 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; +using OrchardCore.Infrastructure.Html; +using OrchardCore.Liquid; +using OrchardCore.Mvc.ModelBinding; using OrchardCore.Notifications.Activities; using OrchardCore.Notifications.ViewModels; using OrchardCore.Workflows.Display; @@ -14,14 +19,21 @@ public class NotifyUserTaskActivityDisplayDriver : Ac where TActivity : NotifyUserTaskActivity where TEditViewModel : NotifyUserTaskActivityViewModel, new() { + private readonly IServiceProvider _serviceProvider; + protected virtual string EditShapeType => $"{nameof(NotifyUserTaskActivity)}_Fields_Edit"; + public NotifyUserTaskActivityDisplayDriver(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public override IDisplayResult Edit(TActivity model) { - return Initialize(EditShapeType, (Func)(viewModel => + return Initialize(EditShapeType, viewModel => { return EditActivityAsync(model, viewModel); - })).Location("Content"); + }).Location("Content"); } public async override Task UpdateAsync(TActivity model, IUpdateModel updater) @@ -29,7 +41,37 @@ public async override Task UpdateAsync(TActivity model, IUpdateM var viewModel = new TEditViewModel(); if (await updater.TryUpdateModelAsync(viewModel, Prefix)) { - await UpdateActivityAsync(viewModel, model); + var liquidTemplateManager = _serviceProvider.GetService(); + + if (liquidTemplateManager != null) + { + var S = _serviceProvider.GetRequiredService>>(); + + if (!liquidTemplateManager.Validate(viewModel.Subject, out var subjectErrors)) + { + updater.ModelState.AddModelError(Prefix, nameof(viewModel.Subject), S["Subject field does not contain a valid Liquid expression. Details: {0}", string.Join(' ', subjectErrors)]); + } + + if (!liquidTemplateManager.Validate(viewModel.Summary, out var summaryErrors)) + { + updater.ModelState.AddModelError(Prefix, nameof(viewModel.Summary), S["Summary field does not contain a valid Liquid expression. Details: {0}", string.Join(' ', summaryErrors)]); + } + + if (!liquidTemplateManager.Validate(viewModel.TextBody, out var textBodyErrors)) + { + updater.ModelState.AddModelError(Prefix, nameof(viewModel.TextBody), S["Text Body field does not contain a valid Liquid expression. Details: {0}", string.Join(' ', textBodyErrors)]); + } + + if (!liquidTemplateManager.Validate(viewModel.HtmlBody, out var htmlBodyErrors)) + { + updater.ModelState.AddModelError(Prefix, nameof(viewModel.HtmlBody), S["HTML Body field does not contain a valid Liquid expression. Details: {0}", string.Join(' ', htmlBodyErrors)]); + } + } + + if (updater.ModelState.IsValid) + { + await UpdateActivityAsync(viewModel, model); + } } return Edit(model); @@ -51,6 +93,7 @@ protected override ValueTask EditActivityAsync(TActivity activity, TEditViewMode protected override void EditActivity(TActivity activity, TEditViewModel model) { model.Subject = activity.Subject.Expression; + model.Summary = activity.Summary.Expression; model.TextBody = activity.TextBody.Expression; model.HtmlBody = activity.HtmlBody.Expression; model.IsHtmlPreferred = activity.IsHtmlPreferred; @@ -71,9 +114,21 @@ protected override Task UpdateActivityAsync(TEditViewModel model, TActivity acti /// protected override void UpdateActivity(TEditViewModel model, TActivity activity) { + var htmlSanitizer = _serviceProvider.GetService(); + + if (htmlSanitizer != null) + { + activity.Summary = new WorkflowExpression(htmlSanitizer.Sanitize(model.Summary)); + activity.HtmlBody = new WorkflowExpression(htmlSanitizer.Sanitize(model.HtmlBody)); + } + else + { + activity.Summary = new WorkflowExpression(model.Summary); + activity.HtmlBody = new WorkflowExpression(model.HtmlBody); + } + activity.Subject = new WorkflowExpression(model.Subject); activity.TextBody = new WorkflowExpression(model.TextBody); - activity.HtmlBody = new WorkflowExpression(model.HtmlBody); activity.IsHtmlPreferred = model.IsHtmlPreferred; } @@ -91,4 +146,8 @@ public override IDisplayResult Display(TActivity activity) public class NotifyUserTaskActivityDisplayDriver : NotifyUserTaskActivityDisplayDriver where TActivity : NotifyUserTaskActivity { + public NotifyUserTaskActivityDisplayDriver(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskDisplayDriver.cs index 0a48ac77866..eb6cf7bc700 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Drivers/NotifyUserTaskDisplayDriver.cs @@ -1,7 +1,12 @@ +using System; using OrchardCore.Notifications.Activities; namespace OrchardCore.Notifications.Drivers; public class NotifyUserTaskDisplayDriver : NotifyUserTaskActivityDisplayDriver { + public NotifyUserTaskDisplayDriver(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Handlers/CoreNotificationEventsHandler.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/Handlers/CoreNotificationEventsHandler.cs index 0968b4973fa..6d9ba40f05d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Handlers/CoreNotificationEventsHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Handlers/CoreNotificationEventsHandler.cs @@ -17,7 +17,7 @@ public override Task CreatingAsync(NotificationContext context) var bodyPart = context.Notification.As(); - bodyPart.Summary = context.NotificationMessage.Summary; + // bodyPart.Summary = context.NotificationMessage.Summary; bodyPart.TextBody = context.NotificationMessage.TextBody; bodyPart.HtmlBody = context.NotificationMessage.HtmlBody; diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/OrchardCore.Notifications.csproj b/src/OrchardCore.Modules/OrchardCore.Notifications/OrchardCore.Notifications.csproj index b727121d8ec..94d05f55ae7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/OrchardCore.Notifications.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/OrchardCore.Notifications.csproj @@ -31,4 +31,8 @@ + + + + diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/ViewModels/NotifyUserTaskActivityViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Notifications/ViewModels/NotifyUserTaskActivityViewModel.cs index 2b00bf5e3ad..68807877ff9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/ViewModels/NotifyUserTaskActivityViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/ViewModels/NotifyUserTaskActivityViewModel.cs @@ -7,6 +7,8 @@ public class NotifyUserTaskActivityViewModel [Required] public string Subject { get; set; } + public string Summary { get; set; } + public string TextBody { get; set; } public string HtmlBody { get; set; } diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyContentOwnerTask.Fields.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyContentOwnerTask.Fields.Edit.cshtml index 36826021dc6..fe237fff8da 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyContentOwnerTask.Fields.Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyContentOwnerTask.Fields.Edit.cshtml @@ -7,9 +7,16 @@ @T["You may use Liquid syntax."] +
+ + + + @T["You may use Liquid syntax. Html is supported."] +
+
- + @T["This optional text body does not support HTML. You may use Liquid syntax."]
diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyUserTaskActivity.Fields.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyUserTaskActivity.Fields.Edit.cshtml index 36826021dc6..fe237fff8da 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyUserTaskActivity.Fields.Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Items/NotifyUserTaskActivity.Fields.Edit.cshtml @@ -7,9 +7,16 @@ @T["You may use Liquid syntax."] +
+ + + + @T["You may use Liquid syntax. Html is supported."] +
+
- + @T["This optional text body does not support HTML. You may use Liquid syntax."]
diff --git a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Notification.Header.cshtml b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Notification.Header.cshtml index 28fe7a3bbfd..20532267866 100644 --- a/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Notification.Header.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Notifications/Views/Notification.Header.cshtml @@ -18,6 +18,8 @@ var readInfo = notification.As(); } -