Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into issue/SNOW-203
Browse files Browse the repository at this point in the history
  • Loading branch information
Piedone committed Sep 4, 2023
2 parents d1cda7f + 687a3ce commit 85ac51a
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;

namespace Lombiq.HelpfulLibraries.AspNetCore.Localization;

public class LocalizedHtmlStringConverter : JsonConverter<LocalizedHtmlString>
{
public override void WriteJson(JsonWriter writer, LocalizedHtmlString value, JsonSerializer serializer)
{
writer.WriteStartObject();

writer.WritePropertyName(nameof(LocalizedHtmlString.Name));
writer.WriteValue(value.Name);

writer.WritePropertyName(nameof(LocalizedHtmlString.Value));
writer.WriteValue(value.Html());

writer.WritePropertyName(nameof(LocalizedHtmlString.IsResourceNotFound));
writer.WriteValue(value.IsResourceNotFound);

writer.WriteEndObject();
}

public override LocalizedHtmlString ReadJson(
JsonReader reader,
Type objectType,
LocalizedHtmlString existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
var token = JToken.Load(reader);

if (token.Type == JTokenType.String)
{
var text = token.Value<string>();
return new LocalizedHtmlString(text, text);
}

if (token is JObject jObject)
{
var name = jObject.GetMaybe(nameof(LocalizedHtmlString.Name))?.ToObject<string>();
var value = jObject.GetMaybe(nameof(LocalizedHtmlString.Value))?.ToObject<string>() ?? name;
var isResourceNotFound = jObject.GetMaybe(nameof(LocalizedHtmlString.IsResourceNotFound))?.ToObject<bool>();

name ??= value;
if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Missing name.");

return new LocalizedHtmlString(name, value, isResourceNotFound == true);
}

throw new InvalidOperationException($"Can't parse token \"{token}\". It should be an object or a string");
}
}
19 changes: 19 additions & 0 deletions Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,23 @@ public static IList<Range> WithoutOverlappingRanges(

return ranges;
}

/// <summary>
/// If the <paramref name="enumerable"/> is not empty, invokes the <paramref name="actionAsync"/> on the first item.
/// </summary>
public static Task InvokeFirstOrCompletedAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> actionAsync) =>
enumerable.FirstOrDefault() is { } item
? actionAsync(item)
: Task.CompletedTask;

/// <summary>
/// If the <paramref name="enumerable"/> is not empty, invokes the <paramref name="funcAsync"/> on the first item
/// and returns its result, otherwise returns <see langword="default"/> for <typeparamref name="TResult"/>.
/// </summary>
public static Task<TResult> InvokeFirstOrDefaultAsync<TItem, TResult>(
this IEnumerable<TItem> enumerable,
Func<TItem, Task<TResult>> funcAsync) =>
enumerable.FirstOrDefault() is { } item
? funcAsync(item)
: Task.FromResult(default(TResult));
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public static class CommonContentDisplayTypes
public const string Summary = nameof(Summary);
public const string SummaryAdmin = nameof(SummaryAdmin);
public const string Thumbnail = nameof(Thumbnail);
public const string Design = nameof(Design);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static Task<string> GetItemEditUrlAsync(this IOrchardHelper orchardHelper
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemEditUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
orchardHelper.GetItemEditUrl(contentItem.ContentItemId);
orchardHelper.GetItemEditUrl(contentItem?.ContentItemId);

/// <summary>
/// Gets the given content item's edit URL.
Expand All @@ -49,7 +49,7 @@ public static Task<string> GetItemDisplayUrlAsync(this IOrchardHelper orchardHel
/// </summary>
[SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "It only returns relative URL.")]
public static string GetItemDisplayUrl(this IOrchardHelper orchardHelper, ContentItem contentItem) =>
orchardHelper.GetItemDisplayUrl(contentItem.ContentItemId);
orchardHelper.GetItemDisplayUrl(contentItem?.ContentItemId);

/// <summary>
/// Gets the given content item's display URL.
Expand Down
10 changes: 10 additions & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Docs/Workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Lombiq Helpful Libraries - Orchard Core Libraries - Workflows for Orchard Core

## Extensions

- `WorkflowManagerExtensions`: Adds `IWorkflowManager` extension methods for specific workflow events and `IEnumerable<IWorkflowManager>` extension methods for triggering workflow events only if there is a workflow manager.

## Activities

- `SimpleEventActivityBase`: A base class for a simple workflow event that only has a `Done` result.
- `SimpleEventActivityDisplayDriverBase`: A base class for a simple workflow event driver that only displays a title, description and optional icon in a conventional format.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ItemGroup>
<PackageReference Include="OrchardCore.Alias" Version="1.6.0" />
<PackageReference Include="OrchardCore.Autoroute" Version="1.6.0" />
<PackageReference Include="OrchardCore.Html" Version="1.6.0" />
<PackageReference Include="OrchardCore.Markdown" Version="1.6.0" />
<PackageReference Include="OrchardCore.Media.Core" Version="1.6.0" />
<PackageReference Include="OrchardCore.Taxonomies" Version="1.6.0" />
Expand All @@ -42,6 +43,7 @@
<!-- Necessary so tag helpers will work in the projects depending on this. -->
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.6.0" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.6.0" />
<PackageReference Include="OrchardCore.Workflows.Abstractions" Version="1.6.0" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ For general details about and on using the Helpful Libraries see the [root Readm
- [Shapes](Docs/Shapes.md)
- [TagHelpers](Docs/TagHelpers.md)
- [Users](Docs/Users.md)
- [Workflow](Docs/Workflows.md)
24 changes: 24 additions & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Shapes/DriverExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Newtonsoft.Json.Linq;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Html.Models;
using OrchardCore.Html.ViewModels;
using System;

namespace OrchardCore.DisplayManagement.Handlers;

public static class DriverExtensions
{
public static ShapeResult RawHtml(this DisplayDriverBase driver, string html) =>
driver.Initialize<HtmlBodyPartViewModel>(nameof(HtmlBodyPart), model =>
{
model.Html = html;
model.ContentItem = new ContentItem { ContentType = nameof(RawHtml) };
model.HtmlBodyPart = new HtmlBodyPart { Html = model.Html, ContentItem = model.ContentItem };
model.TypePartDefinition = new ContentTypePartDefinition(
nameof(RawHtml),
new ContentPartDefinition(nameof(RawHtml), Array.Empty<ContentPartFieldDefinition>(), new JObject()),
new JObject());
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Models;
using System.Collections.Generic;

namespace Lombiq.HelpfulLibraries.OrchardCore.Workflow;

/// <summary>
/// A base class for a simple workflow event that only has a <c>Done</c> result.
/// </summary>
public abstract class SimpleEventActivityBase : EventActivity
{
protected readonly IStringLocalizer T;

public override string Name => GetType().Name;

public abstract override LocalizedString DisplayText { get; }
public abstract override LocalizedString Category { get; }

protected SimpleEventActivityBase(IStringLocalizer stringLocalizer) =>
T = stringLocalizer;

public override IEnumerable<Outcome> GetPossibleOutcomes(
WorkflowExecutionContext workflowContext,
ActivityContext activityContext) =>
new[] { new Outcome(T["Done"]) };

public override ActivityExecutionResult Resume(
WorkflowExecutionContext workflowContext,
ActivityContext activityContext) =>
Outcomes("Done");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Lombiq.HelpfulLibraries.OrchardCore.Contents;
using Microsoft.AspNetCore.Mvc.Localization;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Html;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Helpers;

namespace Lombiq.HelpfulLibraries.OrchardCore.Workflow;

/// <summary>
/// A base class for a simple workflow event driver that only displays a title, description and optional icon in a
/// conventional format.
/// </summary>
public abstract class SimpleEventActivityDisplayDriverBase<TActivity> : DisplayDriver<IActivity, TActivity>
where TActivity : class, IActivity
{
public virtual string IconClass { get; }
public virtual LocalizedHtmlString Title { get; }
public abstract LocalizedHtmlString Description { get; }

private string IconHtml => string.IsNullOrEmpty(IconClass) ? string.Empty : $"<i class=\"fa {IconClass}\"></i>";

public override IDisplayResult Display(TActivity model) =>
Combine(
this.RawHtml(ThumbnailHtml(model)).Location(CommonContentDisplayTypes.Thumbnail, CommonLocationNames.Content),
this.RawHtml(DesignHtml(model)).Location(CommonContentDisplayTypes.Design, CommonLocationNames.Content));

private string ThumbnailHtml(TActivity model) =>
$"<h4 class=\"card-title\">{IconHtml}{GetTitle(model)}</h4><p>{Description?.Html()}</p>";

private string DesignHtml(TActivity model) =>
$"<header><h4>{IconHtml}{GetTitle(model)}</h4></header>";

private string GetTitle(TActivity model)
{
var title = model.GetTitleOrDefault(() => Title).Html();

return string.IsNullOrWhiteSpace(title)
? new HtmlContentString(model.DisplayText).Html()
: title;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using OrchardCore.ContentManagement;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Services;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Lombiq.HelpfulLibraries.OrchardCore.Workflow;

public static class WorkflowManagerExtensions
{
/// <summary>
/// Triggers an event by passing <paramref name="content"/>'s <see cref="ContentItem"/>.
/// </summary>
/// <typeparam name="T">
/// The type of the activity to trigger. This will only work when it's the same as the event's type name which is
/// customary in most events and enforced in <see cref="SimpleEventActivityBase"/> events.
/// </typeparam>
public static Task TriggerContentItemEventAsync<T>(this IWorkflowManager workflowManager, IContent content)
where T : IEvent
{
var contentItem = content.ContentItem;
return workflowManager.TriggerEventAsync(
typeof(T).Name,
contentItem,
$"{contentItem.ContentType}-{contentItem.ContentItemId}");
}

/// <inheritdoc cref="TriggerContentItemEventAsync{T}(IWorkflowManager, IContent)"/>
/// <remarks><para>Executes on the first item of <paramref name="workflowManagers"/> if any.</para></remarks>
public static Task TriggerContentItemEventAsync<T>(this IEnumerable<IWorkflowManager> workflowManagers, IContent content)
where T : IEvent =>
workflowManagers.InvokeFirstOrCompletedAsync(manager => manager.TriggerContentItemEventAsync<T>(content));

/// <summary>
/// Triggers the <see cref="IEvent"/> identified by <paramref name="name"/>.
/// </summary>
/// <remarks><para>Executes on the first item of <paramref name="workflowManagers"/> if any.</para></remarks>
public static Task TriggerEventAsync(
this IEnumerable<IWorkflowManager> workflowManagers,
string name,
object input = null,
string correlationId = null) =>
workflowManagers.InvokeFirstOrCompletedAsync(manager => manager.TriggerEventAsync(name, input, correlationId));

/// <summary>
/// Triggers the <see cref="IEvent"/> identified by <typeparamref name="T"/>.
/// </summary>
public static Task TriggerEventAsync<T>(
this IWorkflowManager workflowManager,
object input = null,
string correlationId = null)
where T : IEvent =>
workflowManager.TriggerEventAsync(typeof(T).Name, input, correlationId);

/// <summary>
/// Triggers the <see cref="IEvent"/> identified by <typeparamref name="T"/>.
/// </summary>
/// <remarks><para>Executes on the first item of <paramref name="workflowManagers"/> if any.</para></remarks>
public static Task TriggerEventAsync<T>(
this IEnumerable<IWorkflowManager> workflowManagers,
object input = null,
string correlationId = null)
where T : IEvent =>
workflowManagers.TriggerEventAsync(typeof(T).Name, input, correlationId);
}

0 comments on commit 85ac51a

Please sign in to comment.