Fix #4358: RichTextEditor Provider Abstraction.

This commit is contained in:
Ben
2024-07-01 17:11:26 +08:00
parent 1eafed755d
commit e00c261777
21 changed files with 726 additions and 342 deletions

View File

@ -1,11 +1,12 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@implements ITextEditor
@inject IStringLocalizer<QuillTextEditor> Localizer
@inject ISettingService SettingService
@inject IStringLocalizer<QuillJSTextEditor> Localizer
<div class="quill-text-editor">
<TabStrip ActiveTab="@_activetab">
@if (AllowRichText)
@if (_allowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
@ -15,7 +16,7 @@
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowFileManagement)
@if (_allowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@ -28,9 +29,9 @@
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
@if (!string.IsNullOrEmpty(_toolbarContent))
{
@ToolbarContent
@((MarkupString)_toolbarContent)
}
else
{
@ -66,7 +67,7 @@
</div>
</TabPanel>
}
@if (AllowRawHtml)
@if (_allowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
@ -76,7 +77,7 @@
<br />
}
<div class="d-flex justify-content-center mb-2">
@if (AllowFileManagement)
@if (_allowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@ -106,6 +107,14 @@
private FileManager _fileManager;
private string _activetab = "Rich";
private bool _allowFileManagement = false;
private bool _allowRawHtml = false;
private bool _allowRichText = false;
private string _theme = "snow";
private string _debugLevel = "info";
private string _toolbarContent = string.Empty;
private bool _settingsLoaded;
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _richfilemanager = false;
@ -121,38 +130,22 @@
private bool _contentchanged = false;
private int _editorIndex;
[Parameter]
public bool AllowFileManagement{ get; set; }
[Parameter]
public bool AllowRichText { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public string Placeholder { get; set; }
[Parameter]
public string Theme { get; set; } = "snow";
[Parameter]
public string DebugLevel { get; set; } = "info";
[Parameter]
public RenderFragment ToolbarContent { get; set; }
public override List<Resource> Resources { get; set; } = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
};
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
interop = new QuillEditorInterop(JSRuntime);
@ -160,11 +153,13 @@
{
Placeholder = Localizer["Placeholder"];
}
await LoadSettings();
}
protected override void OnParametersSet()
{
if (!AllowRichText)
if (!_allowRichText)
{
_activetab = "Raw";
}
@ -174,17 +169,17 @@
{
await base.OnAfterRenderAsync(firstRender);
if (AllowRichText)
if (_allowRichText)
{
if (firstRender)
if (_settingsLoaded && !_initialized)
{
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
_theme,
_debugLevel);
await interop.LoadEditorContent(_editorElement, _richhtml);
@ -202,6 +197,8 @@
// reload editor if Content passed to component has changed
await interop.LoadEditorContent(_editorElement, _richhtml);
_originalrichhtml = await interop.GetHtml(_editorElement);
_contentchanged = false;
}
else
{
@ -215,8 +212,6 @@
}
}
}
_contentchanged = false;
}
}
@ -224,10 +219,14 @@
{
_richhtml = content;
_rawhtml = content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = "";
_richhtml = content;
_contentchanged = content != _originalrawhtml;
if (!_contentchanged)
{
_contentchanged = content != _originalrawhtml;
}
_originalrawhtml = _rawhtml; // preserve for comparison later
StateHasChanged();
}
@ -243,7 +242,7 @@
{
var richhtml = "";
if (AllowRichText)
if (_allowRichText)
{
richhtml = await interop.GetHtml(_editorElement);
}
@ -290,7 +289,7 @@
{
var richhtml = "";
if (AllowRichText)
if (_allowRichText)
{
richhtml = await interop.GetHtml(_editorElement);
}
@ -362,4 +361,24 @@
}
StateHasChanged();
}
private async Task LoadSettings()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowFileManagement = SettingService.GetSetting(settings, "QuillTextEditor_AllowFileManagement", "true") == "true";
_allowRawHtml = SettingService.GetSetting(settings, "QuillTextEditor_AllowRawHtml", "true") == "true";
_allowRichText = SettingService.GetSetting(settings, "QuillTextEditor_AllowRichText", "true") == "true";
_theme = SettingService.GetSetting(settings, "QuillTextEditor_Theme", "snow");
_debugLevel = SettingService.GetSetting(settings, "QuillTextEditor_DebugLevel", "info");
_toolbarContent = SettingService.GetSetting(settings, "QuillTextEditor_ToolbarContent", string.Empty);
_settingsLoaded = true;
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -0,0 +1,99 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inject ISettingService SettingService
@implements Oqtane.Interfaces.ISettingsControl
@inject IStringLocalizer<QuillJSTextEditorSettings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowFileManagement" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
<div class="col-sm-9">
<input type="checkbox" id="AllowFileManagement" class="form-check-input" @bind="_allowFileManagement" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowRawHtml" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
<div class="col-sm-9">
<input type="checkbox" id="AllowRawHtml" class="form-check-input" @bind="_allowRawHtml" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="AllowRichText" ResourceKey="AllowRichText" ResourceType="@resourceType" HelpText="Specify If Editors Can Use Rich Text Editor">Allow Rich Text: </Label>
<div class="col-sm-9">
<input type="checkbox" id="AllowRichText" class="form-check-input" @bind="_allowRichText" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="Theme" ResourceKey="Theme" ResourceType="@resourceType" HelpText="Specify the Rich Text Editor's Theme">Theme: </Label>
<div class="col-sm-9">
<input type="text" id="Theme" class="form-control" @bind="_theme" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="DebugLevel" ResourceKey="DebugLevel" ResourceType="@resourceType" HelpText="Specify the Debug Level">Debug Level: </Label>
<div class="col-sm-9">
<select id="DebugLevel" class="form-select" @bind="_debugLevel">
@foreach (var level in _debugLevels)
{
<option value="@level">@level</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ToolbarContent" ResourceKey="ToolbarContent" ResourceType="@resourceType" HelpText="Specify the Toolbar Content">Toolbar Content: </Label>
<div class="col-sm-9">
<textarea id="ToolbarContent" class="form-control" @bind="_toolbarContent" rows="5" />
</div>
</div>
</div>
@code {
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditorSettings, Oqtane.Client";
private bool _allowFileManagement;
private bool _allowRawHtml;
private bool _allowRichText;
private string _theme;
private string _debugLevel;
private string _toolbarContent;
private List<string> _debugLevels = new List<string> { "info", "log", "warn", "error" };
protected override async Task OnInitializedAsync()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowFileManagement = SettingService.GetSetting(settings, "QuillTextEditor_AllowFileManagement", "true") == "true";
_allowRawHtml = SettingService.GetSetting(settings, "QuillTextEditor_AllowRawHtml", "true") == "true";
_allowRichText = SettingService.GetSetting(settings, "QuillTextEditor_AllowRichText", "true") == "true";
_theme = SettingService.GetSetting(settings, "QuillTextEditor_Theme", "snow");
_debugLevel = SettingService.GetSetting(settings, "QuillTextEditor_DebugLevel", "info");
_toolbarContent = SettingService.GetSetting(settings, "QuillTextEditor_ToolbarContent", string.Empty);
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
public async Task UpdateSettings()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagement.ToString().ToLower());
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtml.ToString().ToLower());
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichText.ToString().ToLower());
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _theme);
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevel);
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContent);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
}
catch (Exception ex)
{
AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -4,7 +4,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IServiceProvider ServiceProvider
@inject ISettingService SettingService
@inject IStringLocalizer<RichTextEditor> Localizer
<div class="row" style="margin-bottom: 50px;">
@ -14,7 +13,7 @@
</div>
@code {
private ITextEditorProvider _textEditorProvider;
private string _textEditorProvider;
private RenderFragment _textEditorComponent;
private ITextEditor _textEditor;
@ -28,29 +27,14 @@
public string Placeholder { get; set; }
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public string Provider { get; set; }
[Parameter]
public bool AllowRichText { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter]
public string Theme { get; set; }
[Parameter]
public string DebugLevel { get; set; }
public override List<Resource> Resources { get; set; } = new List<Resource>();
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
protected override async Task OnInitializedAsync()
{
_textEditorProvider = await GetTextEditorProvider();
_textEditorProvider = await GetTextEditorType(ServiceProvider, PageState.Site.SiteId);
}
protected override void OnParametersSet()
@ -71,6 +55,22 @@
await base.OnAfterRenderAsync(firstRender);
}
public override async Task<List<Resource>> GetResources(IServiceProvider serviceProvider, Page page)
{
var type = await GetTextEditorType(serviceProvider, page.SiteId);
if (!string.IsNullOrEmpty(type))
{
var editorType = Type.GetType(type);
if (editorType != null && editorType.GetInterfaces().Contains(typeof(IModuleControl)))
{
var control = Activator.CreateInstance(editorType) as IModuleControl;
return await control?.GetResources(serviceProvider, page);
}
}
return await base.GetResources(serviceProvider, page);
}
public async Task<string> GetHtml()
{
return await _textEditor.GetContent();
@ -78,37 +78,32 @@
private void CreateTextEditor(RenderTreeBuilder builder)
{
if(_textEditorProvider != null)
if(!string.IsNullOrEmpty(_textEditorProvider))
{
var editorType = Type.GetType(_textEditorProvider.EditorType);
var editorType = Type.GetType(_textEditorProvider);
if (editorType != null)
{
builder.OpenComponent(0, editorType);
//set editor parameters if available.
var attributes = new Dictionary<string, object>
{
{ "AllowFileManagement", AllowFileManagement },
{ "AllowRichText", AllowRichText },
{ "AllowRawHtml", AllowRawHtml },
{ "Placeholder", Placeholder },
{ "ReadOnly", ReadOnly }
};
if(!string.IsNullOrEmpty(Theme))
if (AdditionalAttributes != null)
{
attributes.Add("Theme", Theme);
}
if (!string.IsNullOrEmpty(DebugLevel))
{
attributes.Add("DebugLevel", DebugLevel);
}
if (!string.IsNullOrEmpty(Placeholder))
{
attributes.Add("Placeholder", Placeholder);
}
if(ToolbarContent != null)
{
attributes.Add("ToolbarContent", ToolbarContent);
foreach(var key in AdditionalAttributes.Keys)
{
if(!attributes.ContainsKey(key))
{
attributes.Add(key, AdditionalAttributes[key]);
}
else
{
attributes[key] = AdditionalAttributes[key];
}
}
}
var index = 1;
@ -129,26 +124,21 @@
}
}
private async Task<ITextEditorProvider> GetTextEditorProvider()
private async Task<string> GetTextEditorType(IServiceProvider serviceProvider, int siteId)
{
const string DefaultEditorName = "Quill";
const string EditorSettingName = "TextEditorProvider";
var editorName = await GetTextEditorName(DefaultEditorName);
var editorProviders = ServiceProvider.GetServices<ITextEditorProvider>();
var editorProvider = editorProviders.FirstOrDefault(i => i.Name == editorName);
if(editorProvider == null)
if(!string.IsNullOrEmpty(Provider))
{
editorProvider = editorProviders.FirstOrDefault(i => i.Name == DefaultEditorName);
var provider = serviceProvider.GetServices<ITextEditorProvider>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
if(provider != null)
{
return provider.EditorType;
}
}
return editorProvider;
}
private async Task<string> GetTextEditorName(string defaultName)
{
const string EditorSettingName = "TextEditor";
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
return SettingService.GetSetting(settings, EditorSettingName, defaultName);
var settingService = serviceProvider.GetService<ISettingService>();
var settings = await settingService.GetSiteSettingsAsync(siteId);
return settingService.GetSetting(settings, EditorSettingName, Constants.DefaultTextEditorProvider);
}
}

View File

@ -0,0 +1,31 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@implements ITextEditor
<div class="text-area-editor">
<textarea @bind="_content" @ref="_editor" placeholder="@Placeholder" readonly="@ReadOnly" />
</div>
@code {
private ElementReference _editor;
private string _content;
[Parameter]
public bool ReadOnly { get; set; }
[Parameter]
public string Placeholder { get; set; }
public void Initialize(string content)
{
_content = content;
StateHasChanged();
}
public async Task<string> GetContent()
{
await Task.CompletedTask;
return _content;
}
}