Merge pull request #2373 from sbwalker/dev

Improvements to richtexteditor to allow file management in raw html editor. Also allow disabling of raw html editor which can be utilized via new setting in Html/Text module.
This commit is contained in:
Shaun Walker 2022-08-19 15:34:43 -04:00 committed by GitHub
commit b49432802b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 109 deletions

View File

@ -5,79 +5,100 @@
<div class="row" style="margin-bottom: 50px;"> <div class="row" style="margin-bottom: 50px;">
<div class="col"> <div class="col">
<TabStrip> <TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor"> <TabPanel Name="Rich" Heading="Rich Text Editor">
@if (AllowFileManagement) @if (_richfilemanager)
{ {
@if (_filemanagervisible) <FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
{ <ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" /> <br />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage> }
<br /> <div class="d-flex justify-content-center mb-2">
} @if (AllowRawHtml)
<div class="d-flex justify-content-center mb-2"> {
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp; <button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button> }
@if (_filemanagervisible) @if (AllowFileManagement)
{ {
@((MarkupString)"&nbsp;&nbsp;") <button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button> }
} @if (_richfilemanager)
</div> {
} @((MarkupString)"&nbsp;&nbsp;")
<div class="row"> <button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
<div class="col"> }
<div @ref="@_toolBar"> </div>
@if (ToolbarContent != null) <div class="row">
{ <div class="col">
@ToolbarContent <div @ref="@_toolBar">
} @if (ToolbarContent != null)
else {
{ @ToolbarContent
<select class="ql-header"> }
<option selected=""></option> else
<option value="1"></option> {
<option value="2"></option> <select class="ql-header">
<option value="3"></option> <option selected=""></option>
<option value="4"></option> <option value="1"></option>
<option value="5"></option> <option value="2"></option>
</select> <option value="3"></option>
<span class="ql-formats"> <option value="4"></option>
<button class="ql-bold"></button> <option value="5"></option>
<button class="ql-italic"></button> </select>
<button class="ql-underline"></button> <span class="ql-formats">
<button class="ql-strike"></button> <button class="ql-bold"></button>
</span> <button class="ql-italic"></button>
<span class="ql-formats"> <button class="ql-underline"></button>
<select class="ql-color"></select> <button class="ql-strike"></button>
<select class="ql-background"></select> </span>
</span> <span class="ql-formats">
<span class="ql-formats"> <select class="ql-color"></select>
<button class="ql-list" value="ordered"></button> <select class="ql-background"></select>
<button class="ql-list" value="bullet"></button> </span>
</span> <span class="ql-formats">
<span class="ql-formats"> <button class="ql-list" value="ordered"></button>
<button class="ql-link"></button> <button class="ql-list" value="bullet"></button>
</span> </span>
} <span class="ql-formats">
</div> <button class="ql-link"></button>
<div @ref="@_editorElement"> </span>
</div> }
</div> </div>
</div> <div @ref="@_editorElement">
</TabPanel> </div>
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor"> </div>
<div class="d-flex justify-content-center mb-2"> </div>
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button> </TabPanel>
</div> @if (AllowRawHtml)
@if (ReadOnly) {
{ <TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea> @if (_rawfilemanager)
} {
else <FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
{ <ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea> <br />
} }
</TabPanel> <div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@if (_rawfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
}
</div>
@if (ReadOnly)
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
}
</TabStrip> </TabStrip>
</div> </div>
</div> </div>
@ -85,10 +106,11 @@
@code { @code {
private ElementReference _editorElement; private ElementReference _editorElement;
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _filemanagervisible = false; private bool _richfilemanager = false;
private FileManager _fileManager; private FileManager _fileManager;
private string _richhtml = string.Empty; private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty; private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty; private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty; private string _originalrawhtml = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
@ -102,6 +124,12 @@
[Parameter] [Parameter]
public string Placeholder { get; set; } = "Enter Your Content..."; public string Placeholder { get; set; } = "Enter Your Content...";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor // parameters only applicable to rich text editor
[Parameter] [Parameter]
public RenderFragment ToolbarContent { get; set; } public RenderFragment ToolbarContent { get; set; }
@ -112,9 +140,6 @@
[Parameter] [Parameter]
public string DebugLevel { get; set; } = "info"; public string DebugLevel { get; set; } = "info";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" }, new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
@ -152,9 +177,16 @@
} }
} }
public void CloseFileManager() public void CloseRichFileManager()
{ {
_filemanagervisible = false; _richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty; _message = string.Empty;
StateHasChanged(); StateHasChanged();
} }
@ -194,29 +226,55 @@
return _originalrawhtml; return _originalrawhtml;
} }
} }
} }
public async Task InsertImage() public async Task InsertRichImage()
{ {
_message = string.Empty; _message = string.Empty;
if (_filemanagervisible) if (_richfilemanager)
{ {
var file = _fileManager.GetFile(); var file = _fileManager.GetFile();
if (file != null) if (file != null)
{ {
var interop = new RichTextEditorInterop(JSRuntime); var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, file.Name); await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_filemanagervisible = false; _richfilemanager = false;
} }
else else
{ {
_message = Localizer["Message.Require.Image"]; _message = Localizer["Message.Require.Image"];
} }
} }
else else
{ {
_filemanagervisible = true; _richfilemanager = true;
} }
StateHasChanged(); StateHasChanged();
} }
public async Task InsertRawImage()
{
_message = string.Empty;
if (_rawfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor");
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\">";
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
_rawfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_rawfilemanager = true;
}
StateHasChanged();
}
} }

View File

@ -13,7 +13,7 @@
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit"> <TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
@if (_content != null) @if (_content != null)
{ {
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor> <RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ -60,6 +60,7 @@
private RichTextEditor RichTextEditorHtml; private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement; private bool _allowfilemanagement;
private bool _allowrawhtml;
private string _content = null; private string _content = null;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
@ -73,6 +74,7 @@
try try
{ {
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true")); _allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
await LoadContent(); await LoadContent();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -15,18 +15,29 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowrawhtml">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div> </div>
@code { @code {
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
private string _allowfilemanagement; private string _allowfilemanagement;
private string _allowrawhtml;
protected override void OnInitialized() protected override void OnInitialized()
{ {
try try
{ {
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"); _allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
} _allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
}
catch (Exception ex) catch (Exception ex)
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
@ -39,7 +50,8 @@
{ {
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement); settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -123,4 +123,10 @@
<data name="AllowFileManagement.Text" xml:space="preserve"> <data name="AllowFileManagement.Text" xml:space="preserve">
<value>Allow File Management: </value> <value>Allow File Management: </value>
</data> </data>
<data name="AllowRawHtml.HelpText" xml:space="preserve">
<value>Specify If Editors Can Enter Raw HTML</value>
</data>
<data name="AllowRawHtml.Text" xml:space="preserve">
<value>Allow Raw HTML:</value>
</data>
</root> </root>

View File

@ -293,5 +293,19 @@ namespace Oqtane.UI
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
public ValueTask<int> GetCaretPosition(string id)
{
try
{
return _jsRuntime.InvokeAsync<int>(
"Oqtane.Interop.getCaretPosition",
id);
}
catch
{
return new ValueTask<int>(-1);
}
}
} }
} }

View File

@ -389,6 +389,11 @@ Oqtane.Interop = {
behavior: "smooth", behavior: "smooth",
block: "start", block: "start",
inline: "nearest" inline: "nearest"
}); });
}
},
getCaretPosition: function (id) {
var element = document.getElementById(id);
return element.selectionStart;
} }
}}; };