From e59d5fd33978a7ddbf968d5bdf6d66acbf86a926 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 5 Sep 2025 20:36:50 +0800 Subject: [PATCH] implement radzen text editor. --- .../OqtaneServiceCollectionExtensions.cs | 12 +- .../Modules/Controls/FileManagerDialog.razor | 45 ++++ .../Controls/RadzenEditorDefinitions.cs | 85 +++++++ .../Modules/Controls/RadzenEditorInterop.cs | 60 +++++ .../Modules/Controls/RadzenEditorSetting.cs | 11 + .../Modules/Controls/RadzenTextEditor.razor | 193 +++++++++++++++ .../Modules/Controls/SettingsDialog.razor | 222 ++++++++++++++++++ Oqtane.Client/Oqtane.Client.csproj | 1 + .../Modules/Controls/RadzenTextEditor.resx | 219 +++++++++++++++++ .../Services/RadzenEditorSettingService.cs | 144 ++++++++++++ .../OqtaneServiceCollectionExtensions.cs | 12 +- .../css/radzentexteditor.override.css | 22 ++ .../js/Interops/RadzenTextEditorInterop.js | 47 ++++ 13 files changed, 1069 insertions(+), 4 deletions(-) create mode 100644 Oqtane.Client/Modules/Controls/FileManagerDialog.razor create mode 100644 Oqtane.Client/Modules/Controls/RadzenEditorDefinitions.cs create mode 100644 Oqtane.Client/Modules/Controls/RadzenEditorInterop.cs create mode 100644 Oqtane.Client/Modules/Controls/RadzenEditorSetting.cs create mode 100644 Oqtane.Client/Modules/Controls/RadzenTextEditor.razor create mode 100644 Oqtane.Client/Modules/Controls/SettingsDialog.razor create mode 100644 Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx create mode 100644 Oqtane.Client/Services/RadzenEditorSettingService.cs create mode 100644 Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/css/radzentexteditor.override.css create mode 100644 Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/js/Interops/RadzenTextEditorInterop.js diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index eae6ba0f..96d68d37 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Localization; using Oqtane.Interfaces; using Oqtane.Providers; using Oqtane.Services; using Oqtane.Shared; +using Radzen; namespace Microsoft.Extensions.DependencyInjection { @@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -59,6 +61,12 @@ namespace Microsoft.Extensions.DependencyInjection // providers services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddRadzenComponents(); + + var localizer = services.BuildServiceProvider().GetService>(); + Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer; return services; } diff --git a/Oqtane.Client/Modules/Controls/FileManagerDialog.razor b/Oqtane.Client/Modules/Controls/FileManagerDialog.razor new file mode 100644 index 00000000..48fdbc37 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/FileManagerDialog.razor @@ -0,0 +1,45 @@ +@namespace Oqtane.Modules.Controls +@using System.IO +@using Radzen +@using Radzen.Blazor +@inject DialogService DialogService +@inject IStringLocalizer Localizer + +
+ +
+
+ +
+
+ + +
+@code { + private FileManager _fileManager; + private string _message = string.Empty; + + [Parameter] + public string Filters { get; set; } + + private void OnCancelClick() + { + DialogService.Close(null); + } + + private void OnOkClick() + { + _message = string.Empty; + var file = _fileManager.GetFile(); + if (file != null) + { + var result = $"\"{file.Name}\""; + DialogService.Close(result); + } + else + { + _message = Localizer["Message.Require.Image"]; + StateHasChanged(); + } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Controls/RadzenEditorDefinitions.cs b/Oqtane.Client/Modules/Controls/RadzenEditorDefinitions.cs new file mode 100644 index 00000000..9fdd778d --- /dev/null +++ b/Oqtane.Client/Modules/Controls/RadzenEditorDefinitions.cs @@ -0,0 +1,85 @@ +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; + +namespace Oqtane.Modules.Controls +{ + public sealed class RadzenEditorDefinitions + { + public static IStringLocalizer Localizer { get; internal set; } + + public const string TransparentBackgroundColor = "rgba(0, 0, 0, 0)"; + + public const string LightBackgroundColor = "rgba(255, 255, 255, 1)"; + + public const string DarkBackgroundColor = "rgba(0, 0, 0, 1)"; + + public const string DefaultTheme = "default"; + + public const string DefaultBackground = "Default"; + + public static readonly IDictionary> ToolbarItems = new Dictionary>() + { + { "AlignCenter", (builder, sequence) => CreateFragment(builder, sequence, "AlignCenter", "RadzenHtmlEditorAlignCenter") }, + { "AlignLeft", (builder, sequence) => CreateFragment(builder, sequence, "AlignLeft", "RadzenHtmlEditorAlignLeft") }, + { "AlignRight", (builder, sequence) => CreateFragment(builder, sequence, "AlignRight", "RadzenHtmlEditorAlignRight") }, + { "Background", (builder, sequence) => CreateFragment(builder, sequence, "Background", "RadzenHtmlEditorBackground") }, + { "Color", (builder, sequence) => CreateFragment(builder, sequence, "Color", "RadzenHtmlEditorColor") }, + { "FontName", (builder, sequence) => CreateFragment(builder, sequence, "FontName", "RadzenHtmlEditorFontName") }, + { "FontSize", (builder, sequence) => CreateFragment(builder, sequence, "FontSize", "RadzenHtmlEditorFontSize") }, + { "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") }, + { "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") }, + { "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") }, + { "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") }, + { "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") }, + { "Link", (builder, sequence) => CreateFragment(builder, sequence, "Link", "RadzenHtmlEditorLink") }, + { "OrderedList", (builder, sequence) => CreateFragment(builder, sequence, "OrderedList", "RadzenHtmlEditorOrderedList") }, + { "Outdent", (builder, sequence) => CreateFragment(builder, sequence, "Outdent", "RadzenHtmlEditorOutdent") }, + { "Redo", (builder, sequence) => CreateFragment(builder, sequence, "Redo", "RadzenHtmlEditorRedo") }, + { "RemoveFormat", (builder, sequence) => CreateFragment(builder, sequence, "RemoveFormat", "RadzenHtmlEditorRemoveFormat") }, + { "Separator", (builder, sequence) => CreateFragment(builder, sequence, "Separator", "RadzenHtmlEditorSeparator") }, + { "Source", (builder, sequence) => CreateFragment(builder, sequence, "Source", "RadzenHtmlEditorSource") }, + { "StrikeThrough", (builder, sequence) => CreateFragment(builder, sequence, "StrikeThrough", "RadzenHtmlEditorStrikeThrough") }, + { "Subscript", (builder, sequence) => CreateFragment(builder, sequence, "Subscript", "RadzenHtmlEditorSubscript") }, + { "Superscript", (builder, sequence) => CreateFragment(builder, sequence, "Superscript", "RadzenHtmlEditorSuperscript") }, + { "Underline", (builder, sequence) => CreateFragment(builder, sequence, "Underline", "RadzenHtmlEditorUnderline") }, + { "Undo", (builder, sequence) => CreateFragment(builder, sequence, "Undo", "RadzenHtmlEditorUndo") }, + { "Unlink", (builder, sequence) => CreateFragment(builder, sequence, "Unlink", "RadzenHtmlEditorUnlink") }, + { "UnorderedList", (builder, sequence) => CreateFragment(builder, sequence, "UnorderedList", "RadzenHtmlEditorUnorderedList") }, + }; + + public static readonly string DefaultToolbarItems = "Undo,Redo,Separator,FontName,FontSize,FormatBlock,Bold,Italic,Underline,StrikeThrough,Separator,AlignLeft,AlignCenter,AlignRight,Justify,Separator,Indent,Outdent,UnorderedList,OrderedList,Separator,Color,Background,RemoveFormat,Separator,Subscript,Superscript,Separator,Link,Unlink,InsertImage,Separator,Source"; + + private static int CreateFragment(RenderTreeBuilder builder, int sequence, string name, string typeName, string commaneName = "", string icon = "") + { + var fullTypeName = $"Radzen.Blazor.{typeName}, Radzen.Blazor"; + var type = Type.GetType(fullTypeName); + if (type != null) + { + var title = Localizer[$"{name}.Title"]; + var placeholder = Localizer[$"{name}.Placeholder"]; + builder.OpenComponent(sequence++, type); + if (!string.IsNullOrEmpty(title) && title != $"{name}.Title" && type.GetProperty("Title") != null) + { + builder.AddAttribute(sequence++, "Title", title); + } + if (!string.IsNullOrEmpty(placeholder) && placeholder != $"{name}.Placeholder" && type.GetProperty("Placeholder") != null) + { + builder.AddAttribute(sequence++, "Placeholder", placeholder); + } + if (!string.IsNullOrEmpty(commaneName) && type.GetProperty("CommandName") != null) + { + builder.AddAttribute(sequence++, "CommandName", commaneName); + } + if (!string.IsNullOrEmpty(icon) && type.GetProperty("Icon") != null) + { + builder.AddAttribute(sequence++, "Icon", icon); + } + builder.CloseComponent(); + } + + return sequence; + } + } +} diff --git a/Oqtane.Client/Modules/Controls/RadzenEditorInterop.cs b/Oqtane.Client/Modules/Controls/RadzenEditorInterop.cs new file mode 100644 index 00000000..b9ddcfb0 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/RadzenEditorInterop.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System.Threading.Tasks; + +namespace Oqtane.Modules.Controls +{ + public class RadzenEditorInterop + { + private readonly IJSRuntime _jsRuntime; + + public RadzenEditorInterop(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + + public Task Initialize(ElementReference editor) + { + try + { + _jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.initialize", editor); + } + catch + { + + } + + return Task.CompletedTask; + } + + public Task SetBackgroundColor(ElementReference editor, string color) + { + try + { + _jsRuntime.InvokeVoidAsync( + "Oqtane.RadzenTextEditor.setBackgroundColor", + editor, color); + } + catch + { + + } + + return Task.CompletedTask; + } + + public Task UpdateDialogLayout(ElementReference editor) + { + try + { + _jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.updateDialogLayout", editor); + } + catch + { + + } + + return Task.CompletedTask; + } + } +} diff --git a/Oqtane.Client/Modules/Controls/RadzenEditorSetting.cs b/Oqtane.Client/Modules/Controls/RadzenEditorSetting.cs new file mode 100644 index 00000000..f6d98f49 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/RadzenEditorSetting.cs @@ -0,0 +1,11 @@ +namespace Oqtane.Modules.Controls +{ + public class RadzenEditorSetting + { + public string Theme { get; set; } + + public string Background { get; set; } + + public string ToolbarItems { get; set; } + } +} diff --git a/Oqtane.Client/Modules/Controls/RadzenTextEditor.razor b/Oqtane.Client/Modules/Controls/RadzenTextEditor.razor new file mode 100644 index 00000000..1b3acd85 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/RadzenTextEditor.razor @@ -0,0 +1,193 @@ +@using Microsoft.Extensions.Configuration +@using Oqtane.Interfaces +@using System.Text.RegularExpressions +@using Radzen +@using Radzen.Blazor + +@namespace Oqtane.Modules.Controls +@inherits ModuleControlBase +@implements ITextEditor +@implements IDisposable +@inject Radzen.ThemeService ThemeService +@inject IRadzenEditorSettingService EditorSettingService +@inject DialogService DialogService +@inject NavigationManager NavigationManager +@inject IStringLocalizer Localizer + + + + + + @_toolbar + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) + { + + } + + + + +@code { + private Oqtane.Modules.Controls.RadzenEditorInterop _interop; + private RadzenHtmlEditor _editor; + private string _value; + private bool _visible = false; + private string _theme; + private string _background; + private IList _toolbarItems; + private RenderFragment _toolbar; + + [Parameter] + public string Placeholder { get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + [Parameter] + public int Height { get; set; } = 450; + + public string Name => "RadzenTextEditor"; + + public override List Resources { get; set; } = new List() + { + new Resource { ResourceType = ResourceType.Script, Url = "/_content/Radzen.Blazor/Radzen.Blazor.js", Location = ResourceLocation.Head }, + new Resource { ResourceType = ResourceType.Script, Url = "/Modules/Oqtane.RadzenTextEditor/Resources/js/Interops/RadzenTextEditorInterop.js", Location = ResourceLocation.Head }, + }; + + protected override void OnInitialized() + { + _interop = new Oqtane.Modules.Controls.RadzenEditorInterop(JSRuntime); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender) + { + var interop = new Interop(JSRuntime); + await interop.IncludeLink("", "stylesheet", $"/Modules/Oqtane.RadzenTextEditor/Resources/css/radzentexteditor.override.css", "text/css", "", "", ""); + await LoadSettings(); + _visible = true; + StateHasChanged(); + + await _interop.Initialize(_editor.Element); + + if (!string.IsNullOrEmpty(_theme)) + { + ThemeService.SetTheme(_theme); + } + + if (!string.IsNullOrEmpty(_background)) + { + var backgroundColor = RadzenEditorDefinitions.TransparentBackgroundColor; + switch (_background) + { + case "Light": + backgroundColor = RadzenEditorDefinitions.LightBackgroundColor; + break; + case "Dark": + backgroundColor = RadzenEditorDefinitions.DarkBackgroundColor; + break; + } + await _interop.SetBackgroundColor(_editor.Element, backgroundColor); + } + } + } + + public void Initialize(string content) + { + _value = !string.IsNullOrEmpty(content) ? content : string.Empty; + DialogService.OnOpen += OnDialogOpened; + } + + public void Dispose() + { + if (DialogService != null) + { + DialogService.OnOpen -= OnDialogOpened; + } + } + + public async Task GetContent() + { + await Task.CompletedTask; + + return _value; + } + + private async Task LoadSettings() + { + var scope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId); + var editorSetting = scope == 1 + ? await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId) + : await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId); + + _theme = editorSetting.Theme; + _background = editorSetting.Background; + _toolbarItems = editorSetting.ToolbarItems.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList(); + _toolbar = SetupToolbarItems(); + } + + private RenderFragment SetupToolbarItems() + { + return builder => + { + var sequence = 0; + foreach (var item in _toolbarItems) + { + if (RadzenEditorDefinitions.ToolbarItems.ContainsKey(item)) + { + sequence = RadzenEditorDefinitions.ToolbarItems[item](builder, sequence); + } + } + }; + } + + private async Task OnExecute(HtmlEditorExecuteEventArgs args) + { + if (args.CommandName == "InsertImage") + { + await InsertImage(args.Editor); + } + else if (args.CommandName == "Settings") + { + await UpdateSettings(args.Editor); + } + } + + private async Task InsertImage(RadzenHtmlEditor editor) + { + await editor.SaveSelectionAsync(); + + var result = await DialogService.OpenAsync(Localizer["DialogTitle.SelectImage"], new Dictionary + { + { "Filters", PageState.Site.ImageFiles } + }); + + await editor.RestoreSelectionAsync(); + + if (result != null) + { + await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result); + } + } + + private async Task UpdateSettings(RadzenHtmlEditor editor) + { + await editor.SaveSelectionAsync(); + + var result = await DialogService.OpenAsync(Localizer["Settings"], null, new DialogOptions { Width = "650px" }); + if (result == true) + { + NavigationManager.NavigateTo(NavigationManager.Uri); + } + + await editor.RestoreSelectionAsync(); + } + + private async void OnDialogOpened(string title, Type componentType, Dictionary parameters, DialogOptions options) + { + await _interop.UpdateDialogLayout(_editor.Element); + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Controls/SettingsDialog.razor b/Oqtane.Client/Modules/Controls/SettingsDialog.razor new file mode 100644 index 00000000..d9ddaca2 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/SettingsDialog.razor @@ -0,0 +1,222 @@ +@namespace Oqtane.Modules.Controls +@using System.IO +@using Radzen +@using Radzen.Blazor +@inherits ModuleControlBase +@inject DialogService DialogService +@inject Radzen.ThemeService ThemeService +@inject ISettingService SettingService +@inject IRadzenEditorSettingService EditorSettingService +@inject IStringLocalizer Localizer + +
+
+ @Localizer["Scope"] +
+
+ + + + + + +
+
+
+
+ @Localizer["Theme"] +
+
+ + + +
+
+
+
+ @Localizer["Background"] +
+
+ + + +
+
+
+
+ @Localizer["Toolbar"] +
+
+
+
+ + +
+
+ + +
+
+
+
+ + + + + + + +
+
+
+
+
+ + +
+ +@code { + private readonly IList _themes = new List + { + "default", + "dark", + "material", + "material-dark", + "standard", + "standard-dark", + "humanistic", + "humanistic-dark", + "software", + "software-dark" + }; + private readonly IList _backgroundColors = new List { "Default", "Light", "Dark" }; + + private int _settingScope; + private string _theme; + private string _background; + private IList _toolbarItems = new List(); + private string _addToolbarItem; + + protected override async Task OnInitializedAsync() + { + _settingScope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId); + + await LoadSettings(); + } + + private async Task LoadSettingsFromModule() + { + return await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId); + } + + private async Task LoadSettingsFromSite() + { + return await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId); + } + + private async Task LoadSettings() + { + var editorSetting = _settingScope == 1 ? await LoadSettingsFromModule() : await LoadSettingsFromSite(); + _theme = editorSetting.Theme; + _background = editorSetting.Background; + _toolbarItems = editorSetting.ToolbarItems.Split(',').Select((v, i) => + { + return new ToolbarItem { Key = i, Name = v }; + }).ToList(); + } + + private async Task OnScopeChanged() + { + await LoadSettings(); + + StateHasChanged(); + } + + private void AddToolbarItem() + { + if (!string.IsNullOrEmpty(_addToolbarItem)) + { + _toolbarItems.Add(new ToolbarItem { Key = _toolbarItems.Count, Name = _addToolbarItem }); + _addToolbarItem = string.Empty; + + StateHasChanged(); + } + } + + private void ResetToolbarItem() + { + _toolbarItems = RadzenEditorDefinitions.DefaultToolbarItems.Split(',').Select((v, i) => + { + return new ToolbarItem { Key = i, Name = v }; + }).ToList(); + + StateHasChanged(); + } + + private void DeleteToolbarItem(ToolbarItem item) + { + _toolbarItems.Remove(item); + + StateHasChanged(); + } + + private void OnCancelClick() + { + DialogService.Close(false); + } + + private async Task OnOkClick() + { + var editorSetting = new RadzenEditorSetting + { + Theme = _theme, + Background = _background, + ToolbarItems = string.Join(",", _toolbarItems.Select(i => i.Name)) + }; + await EditorSettingService.UpdateSettingScopeAsync(ModuleState.ModuleId, _settingScope); + if (_settingScope == 1) + { + await EditorSettingService.SaveModuleSettingsAsync(ModuleState.ModuleId, editorSetting); + } + else + { + await EditorSettingService.SaveSiteSettingsAsync(PageState.Site.SiteId, editorSetting); + } + + DialogService.Close(true); + } + + private void OnToolbarItemDrop(RadzenDropZoneItemEventArgs args) + { + if (args.ToItem != null && args.ToItem.Key != args.Item.Key) + { + _toolbarItems.Remove(args.Item); + _toolbarItems.Insert(_toolbarItems.IndexOf(args.ToItem), args.Item); + } + } + + private void OnToolbarItemRender(RadzenDropZoneItemRenderEventArgs args) + { + args.Attributes.Add("class", "rz-card rz-variant-flat rz-background-color-primary-lighter rz-color-on-primary-lighter rz-p-2 d-inline-block ms-1 mt-1"); + } + + public class ToolbarItem + { + public int Key { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index f35decd2..5ff0e3e1 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -26,6 +26,7 @@ + diff --git a/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx new file mode 100644 index 00000000..4c84d4df --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Editor Background: + + + Close + + + Dark + + + Default + + + Select Image + + + Font + + + Font Name + + + Size + + + Font Size + + + Format + + + Format Block + + + Humanistic + + + Humanistic Dark + + + Insert Image + + + Light + + + Material + + + Material Dark + + + You Must Select An Image To Insert + + + Module + + + Enter Your Content... + + + Settings + + + Scope: + + + Site + + + Software + + + Software Dark + + + Standard + + + Standard Dark + + + Theme: + + + Dark + + + Default + + + Toolbar Items: + + + Add + + + Reset + + \ No newline at end of file diff --git a/Oqtane.Client/Services/RadzenEditorSettingService.cs b/Oqtane.Client/Services/RadzenEditorSettingService.cs new file mode 100644 index 00000000..d3153042 --- /dev/null +++ b/Oqtane.Client/Services/RadzenEditorSettingService.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Oqtane.Models; +using Oqtane.Modules; +using Oqtane.Modules.Controls; +using Oqtane.Services; + +namespace Oqtane.Services +{ + public interface IRadzenEditorSettingService + { + Task GetSettingScopeAsync(int moduleId); + + Task UpdateSettingScopeAsync(int moduleId, int scope); + + Task LoadSettingsFromModuleAsync(int moduleId); + + Task LoadSettingsFromSiteAsync(int siteId); + + Task SaveSiteSettingsAsync(int siteId, RadzenEditorSetting editorSetting); + + Task SaveModuleSettingsAsync(int moduleId, RadzenEditorSetting editorSetting); + } + public class RadzenEditorSettingService : IRadzenEditorSettingService, IService + { + private const string SettingPrefix = "rzeditor:"; + + private readonly ISettingService _settingService; + + public RadzenEditorSettingService(ISettingService settingService) + { + _settingService = settingService; + } + + public async Task GetSettingScopeAsync(int moduleId) + { + var key = $"{SettingPrefix}Scope"; + var settings = await _settingService.GetModuleSettingsAsync(moduleId); + if (settings.ContainsKey(key) && int.TryParse(settings[key], out int value)) + { + return value; + } + + return 0; // site as default + } + + public async Task UpdateSettingScopeAsync(int moduleId, int scope) + { + var settings = new Dictionary + { + { $"{SettingPrefix}Scope", scope.ToString() } + }; + + await _settingService.UpdateModuleSettingsAsync(settings, moduleId); + } + + public async Task LoadSettingsFromModuleAsync(int moduleId) + { + var settings = await _settingService.GetModuleSettingsAsync(moduleId); + return ReadSettings(settings); + } + + public async Task LoadSettingsFromSiteAsync(int siteId) + { + var settings = await _settingService.GetSiteSettingsAsync(siteId); + return ReadSettings(settings); + } + + public async Task SaveSiteSettingsAsync(int siteId, RadzenEditorSetting editorSetting) + { + var settings = CreateSettingsDictionary(editorSetting); + if (settings.Any()) + { + await _settingService.UpdateSiteSettingsAsync(settings, siteId); + } + } + + public async Task SaveModuleSettingsAsync(int moduleId, RadzenEditorSetting editorSetting) + { + var settings = CreateSettingsDictionary(editorSetting); + if (settings.Any()) + { + await _settingService.UpdateModuleSettingsAsync(settings, moduleId); + } + } + + private RadzenEditorSetting ReadSettings(IDictionary settings) + { + var setting = new RadzenEditorSetting + { + Theme = RadzenEditorDefinitions.DefaultTheme, + Background = RadzenEditorDefinitions.DefaultBackground, + ToolbarItems = RadzenEditorDefinitions.DefaultToolbarItems + }; + + if (settings != null) + { + var themeKey = $"{SettingPrefix}Theme"; + var backgroundKey = $"{SettingPrefix}Background"; + var toolbarItemsKey = $"{SettingPrefix}ToolbarItems"; + + if (settings.ContainsKey(themeKey) && !string.IsNullOrEmpty(settings[themeKey])) + { + setting.Theme = settings[themeKey]; + } + + if (settings.ContainsKey(backgroundKey) && !string.IsNullOrEmpty(settings[backgroundKey])) + { + setting.Background = settings[backgroundKey]; + } + + if (settings.ContainsKey(toolbarItemsKey) && !string.IsNullOrEmpty(settings[toolbarItemsKey])) + { + setting.ToolbarItems = settings[toolbarItemsKey]; + } + } + + return setting; + } + + private Dictionary CreateSettingsDictionary(RadzenEditorSetting editorSetting) + { + var settings = new Dictionary(); + + if (!string.IsNullOrEmpty(editorSetting.Theme)) + { + settings.Add($"{SettingPrefix}Theme", editorSetting.Theme); + } + if (!string.IsNullOrEmpty(editorSetting.Background)) + { + settings.Add($"{SettingPrefix}Background", editorSetting.Background); + } + if (!string.IsNullOrEmpty(editorSetting.ToolbarItems)) + { + settings.Add($"{SettingPrefix}ToolbarItems", editorSetting.ToolbarItems); + } + + return settings; + } + } +} diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index ccd9f3a3..dbafdab7 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Oqtane.Extensions; @@ -32,6 +33,7 @@ using Oqtane.Repository; using Oqtane.Security; using Oqtane.Services; using Oqtane.Shared; +using Radzen; namespace Microsoft.Extensions.DependencyInjection { @@ -193,7 +195,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -208,7 +210,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -230,6 +232,12 @@ namespace Microsoft.Extensions.DependencyInjection // providers services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddRadzenComponents(); + + var localizer = services.BuildServiceProvider().GetService>(); + Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer; return services; } diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/css/radzentexteditor.override.css b/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/css/radzentexteditor.override.css new file mode 100644 index 00000000..e142053b --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/css/radzentexteditor.override.css @@ -0,0 +1,22 @@ +.rz-text-editor { + outline: none !important; +} + +.rz-html-editor-dropdown-items, +.rz-popup, +.rz-editor-dialog-wrapper { + z-index: 9999 !important; +} + +.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item, +.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item > * { + color: var(--rz-editor-button-color); +} +.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-value, +.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-trigger, +.rz-text-editor .rz-html-editor-colorpicker .rz-html-editor-color { + color: var(--rz-editor-button-color); +} +.rz-text-editor .rz-colorpicker.rz-state-disabled { + border: none !important; +} \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/js/Interops/RadzenTextEditorInterop.js b/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/js/Interops/RadzenTextEditorInterop.js new file mode 100644 index 00000000..e92c7150 --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.RadzenTextEditor/Resources/js/Interops/RadzenTextEditorInterop.js @@ -0,0 +1,47 @@ +var Oqtane = Oqtane || {}; + +Oqtane.RadzenTextEditor = { + initialize: function (editor) { + if (typeof Radzen.openPopup === "function" && Radzen.openPopup !== Oqtane.RadzenTextEditor.openPopup) { + Oqtane.RadzenTextEditor.radzenOpenPopup = Radzen.openPopup; + Radzen.openPopup = Oqtane.RadzenTextEditor.openPopup; + } + }, + openPopup: function () { + Oqtane.RadzenTextEditor.radzenOpenPopup.apply(this, arguments); + var id = arguments[1]; + var popup = document.getElementById(id); + if (popup) { + Oqtane.RadzenTextEditor.updateButtonStyles(popup); + } + }, + setBackgroundColor: function (editor, color) { + editor.getElementsByClassName("rz-html-editor-content")[0].style.backgroundColor = color; + }, + updateDialogLayout: function (editor) { + var dialogs = editor.parentElement.getElementsByClassName('rz-dialog-wrapper'); + for (var dialog of dialogs) { + document.body.appendChild(dialog); + dialog.classList.add('rz-editor-dialog-wrapper', 'text-dark'); + + this.updateButtonStyles(dialog); + } + }, + updateButtonStyles: function (parent) { + var primaryBtns = parent.getElementsByClassName('rz-primary'); + if (primaryBtns) { + for (var btn of primaryBtns) { + btn.classList.remove('rz-button', 'rz-primary'); + btn.classList.add('btn', 'btn-primary'); + } + } + + var secondaryBtns = parent.getElementsByClassName('rz-secondary'); + if (secondaryBtns) { + for (var btn of secondaryBtns) { + btn.classList.remove('rz-button', 'rz-secondary'); + btn.classList.add('btn', 'btn-secondary'); + } + } + } +} \ No newline at end of file