Skip to content

Commit 4e6e6d0

Browse files
committed
Merge remote-tracking branch 'origin/dev' into issue/OSOE-925
2 parents 6bbc551 + 9677965 commit 4e6e6d0

15 files changed

+414
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
obj/
55
bin/
66
artifacts/
7-
wwwroot/
7+
# Ignore generated vendor assets, keep custom wwwroot source files trackable.
8+
Lombiq.HelpfulExtensions/wwwroot/vendors/*
89
node_modules/
910
*.user
1011
.pnpm-debug.log
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Lombiq.Tests.UI.Extensions;
2+
using Lombiq.Tests.UI.Services;
3+
using OpenQA.Selenium;
4+
using Shouldly;
5+
using System.Threading.Tasks;
6+
7+
namespace Lombiq.HelpfulExtensions.Tests.UI.Extensions;
8+
9+
public static class LucideTestCaseUITestContextExtensions
10+
{
11+
/// <summary>
12+
/// Tests the Lombiq Helpful Extensions - Lucide feature.
13+
/// </summary>
14+
public static async Task TestLucideFeatureAsync(this UITestContext context)
15+
{
16+
const string iconName = "camera";
17+
18+
await context.SignInDirectlyAsync();
19+
20+
await context.EnableFeatureDirectlyAsync(FeatureIds.Lucide);
21+
await context.ExecuteRecipeDirectlyAsync("Lombiq.HelpfulExtensions.Tests.UI.Lucide.Tests");
22+
await context.CreateNewContentItemAsync("LucidePickerTest", onlyIfNotAlreadyThere: false);
23+
24+
await context.ClickReliablyOnAsync(By.CssSelector("[data-lucide-toggle]"));
25+
await context.ClickAndFillInWithRetriesAsync(By.CssSelector("[data-lucide-search]"), iconName);
26+
await context.ClickReliablyOnAsync(By.CssSelector($"[data-lucide-icon='{iconName}']"));
27+
28+
var selectedIcon = context.ExecuteScript(
29+
"return document.querySelector(arguments[0])?.dataset.lucideIcon ?? '';",
30+
$"[data-lucide-icon='{iconName}'].active") as string;
31+
selectedIcon.ShouldBe(iconName);
32+
context.Get(By.CssSelector($"[data-lucide-preview] [data-lucide='{iconName}']"));
33+
}
34+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "Lombiq.HelpfulExtensions.Tests.UI.Lucide.Tests",
3+
"displayName": "Lombiq Helpful Extensions - Lucide test recipe",
4+
"description": "Creates a simple content type for testing the Lucide icon picker editor.",
5+
"author": "Lombiq Technologies",
6+
"website": "https://github.com/Lombiq/Helpful-Extensions",
7+
"version": "1.0",
8+
"issetuprecipe": false,
9+
"categories": [],
10+
"tags": [],
11+
"steps": [
12+
{
13+
"name": "ContentDefinition",
14+
"ContentTypes": [
15+
{
16+
"Name": "LucidePickerTest",
17+
"DisplayName": "Lucide Picker Test",
18+
"Settings": {
19+
"ContentTypeSettings": {
20+
"Creatable": true,
21+
"Listable": true,
22+
"Draftable": true,
23+
"Versionable": true
24+
}
25+
},
26+
"ContentTypePartDefinitionRecords": [
27+
{
28+
"PartName": "LucidePickerTest",
29+
"Name": "LucidePickerTest",
30+
"Settings": {}
31+
}
32+
]
33+
}
34+
],
35+
"ContentParts": [
36+
{
37+
"Name": "LucidePickerTest",
38+
"Settings": {},
39+
"ContentPartFieldDefinitionRecords": [
40+
{
41+
"FieldName": "TextField",
42+
"Name": "Icon",
43+
"Settings": {
44+
"ContentPartFieldSettings": {
45+
"DisplayName": "Icon",
46+
"Editor": "LucideIconPicker"
47+
}
48+
}
49+
}
50+
]
51+
}
52+
]
53+
}
54+
]
55+
}

Lombiq.HelpfulExtensions/Constants/ResourceNames.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ namespace Lombiq.HelpfulExtensions.Constants;
33
public static class ResourceNames
44
{
55
public const string TargetBlank = nameof(TargetBlank);
6+
public const string Lucide = nameof(Lucide);
7+
public const string LucideIconPicker = nameof(LucideIconPicker);
68
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Lombiq.HelpfulLibraries.Attributes;
2+
using Microsoft.Extensions.Options;
3+
using OrchardCore.ResourceManagement;
4+
5+
namespace Lombiq.HelpfulExtensions.Extensions.Lucide;
6+
7+
[LibManVersions]
8+
public partial class LucideResourceManagementOptionsConfiguration : IConfigureOptions<ResourceManagementOptions>
9+
{
10+
private const string ModuleRoot = "~/" + FeatureIds.Base + "/";
11+
private const string Css = ModuleRoot + "css/";
12+
private const string Scripts = ModuleRoot + "js/";
13+
private const string Vendors = ModuleRoot + "vendors/";
14+
private static readonly ResourceManifest _manifest = new();
15+
16+
static LucideResourceManagementOptionsConfiguration()
17+
{
18+
_manifest
19+
.DefineScript(Constants.ResourceNames.Lucide)
20+
.SetUrl(
21+
Vendors + "lucide/dist/umd/lucide.min.js",
22+
Vendors + "lucide/dist/umd/lucide.js")
23+
.SetVersion(LibManVersions.Lucide);
24+
25+
_manifest
26+
.DefineScript(Constants.ResourceNames.LucideIconPicker)
27+
.SetUrl(Scripts + "lucide-icon-picker.js")
28+
.SetDependencies(Constants.ResourceNames.Lucide);
29+
30+
_manifest
31+
.DefineStyle(Constants.ResourceNames.LucideIconPicker)
32+
.SetUrl(Css + "lucide-icon-picker.css");
33+
}
34+
35+
public void Configure(ResourceManagementOptions options) => options.ResourceManifests.Add(_manifest);
36+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Lucide
2+
3+
Adds the [Lucide](https://lucide.dev/) icon library integration.
4+
5+
## Lucide resource
6+
7+
The `Lucide` script resource registers the Lucide UMD bundle so themes and modules can render `data-lucide` icons and call `window.lucide.createIcons()`.
8+
9+
## Lucide Icon Picker
10+
11+
`LucideIconPicker` is a `TextField` editor flavor similar to Orchard Core's built-in `IconPicker`, but it selects Lucide icon names instead of Font Awesome names.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Options;
3+
using OrchardCore.Modules;
4+
using OrchardCore.ResourceManagement;
5+
6+
namespace Lombiq.HelpfulExtensions.Extensions.Lucide;
7+
8+
[Feature(FeatureIds.Lucide)]
9+
public sealed class Startup : StartupBase
10+
{
11+
public override void ConfigureServices(IServiceCollection services) =>
12+
services.AddTransient<IConfigureOptions<ResourceManagementOptions>, LucideResourceManagementOptionsConfiguration>();
13+
}

Lombiq.HelpfulExtensions/FeatureIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static class FeatureIds
1212
public const string Flows = FeatureIdPrefix + nameof(Flows);
1313
public const string GoogleTag = FeatureIdPrefix + nameof(GoogleTag);
1414
public const string Liquid = FeatureIdPrefix + nameof(Liquid);
15+
public const string Lucide = FeatureIdPrefix + nameof(Lucide);
1516
public const string OrchardRecipeMigration = FeatureIdPrefix + nameof(OrchardRecipeMigration);
1617
public const string ResetPasswordActivity = Workflows + "." + nameof(ResetPasswordActivity);
1718
public const string Security = FeatureIdPrefix + nameof(Security);

Lombiq.HelpfulExtensions/Manifest.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,15 @@
175175
Category = "Liquid",
176176
Description = "Adds various Liquid tags and filters."
177177
)]
178+
179+
[assembly: Feature(
180+
Id = Lucide,
181+
Name = "Lombiq Helpful Extensions - Lucide",
182+
Category = "Content",
183+
Description = "Adds the Lucide icon library as a resource and a TextField editor for picking Lucide icons.",
184+
Dependencies =
185+
[
186+
"OrchardCore.ContentFields",
187+
"OrchardCore.Resources",
188+
]
189+
)]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@model OrchardCore.ContentFields.ViewModels.EditTextFieldViewModel
2+
@using OrchardCore
3+
@using OrchardCore.ContentFields.Settings
4+
@using OrchardCore.ContentManagement.Metadata.Models
5+
@using static Lombiq.HelpfulExtensions.Constants.ResourceNames
6+
@{
7+
var settings = Model.PartFieldDefinition.GetSettings<TextFieldSettings>();
8+
}
9+
10+
<style asp-name="@LucideIconPicker"></style>
11+
<script asp-name="@LucideIconPicker" at="Foot"></script>
12+
13+
<div class="@Orchard.GetFieldWrapperClasses(Model.PartFieldDefinition)" id="@Html.IdFor(x => x.Text)_FieldWrapper">
14+
<label asp-for="Text" class="@Orchard.GetLabelClasses(inputRequired: settings.Required)">@Model.PartFieldDefinition.DisplayName()</label>
15+
<div class="@Orchard.GetEndClasses()">
16+
<input type="hidden" asp-for="Text" data-lucide-value class="content-preview-text" />
17+
<div class="lucide-icon-picker dropdown d-inline-block" data-lucide-icon-picker>
18+
<div class="btn-group" role="group" aria-label="@T["Lucide icon picker"]">
19+
<button
20+
type="button"
21+
class="btn btn-primary dropdown-toggle lucide-icon-picker__selected"
22+
data-bs-toggle="dropdown"
23+
data-bs-display="static"
24+
data-lucide-toggle
25+
aria-expanded="false"
26+
aria-label="@T["Open icon picker"]">
27+
<span class="lucide-icon-picker__preview" data-lucide-preview aria-hidden="true"></span>
28+
<span class="visually-hidden">@T["Open icon picker"]</span>
29+
</button>
30+
<div class="dropdown-menu p-3 shadow lucide-icon-picker__menu" data-lucide-menu>
31+
<div class="d-flex align-items-center gap-2 mb-2">
32+
<input type="search" class="form-control form-control-sm" placeholder="@T["Search icons..."]" data-lucide-search />
33+
<button type="button" class="btn btn-sm btn-outline-secondary" data-lucide-clear>@T["Clear"]</button>
34+
</div>
35+
<div class="lucide-icon-picker__grid" data-lucide-grid role="listbox" aria-label="@T["Lucide icons"]"></div>
36+
<p class="lucide-icon-picker__empty d-none m-0" data-lucide-empty>@T["No icons found."]</p>
37+
</div>
38+
</div>
39+
</div>
40+
41+
<span asp-validation-for="Text"></span>
42+
@if (!string.IsNullOrEmpty(settings.Hint))
43+
{
44+
<span class="hint">@settings.Hint</span>
45+
}
46+
</div>
47+
</div>

0 commit comments

Comments
 (0)