Merge pull request #4317 from zyhfish/task/fix-issue-4316
Fix #4316: add text editor interfaces.
This commit is contained in:
commit
175675ad99
@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Oqtane.Modules.Controls
|
namespace Oqtane.Modules.Controls
|
||||||
{
|
{
|
||||||
public class RichTextEditorInterop
|
public class QuillEditorInterop
|
||||||
{
|
{
|
||||||
private readonly IJSRuntime _jsRuntime;
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
public RichTextEditorInterop(IJSRuntime jsRuntime)
|
public QuillEditorInterop(IJSRuntime jsRuntime)
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
}
|
}
|
365
Oqtane.Client/Modules/Controls/QuillTextEditor.razor
Normal file
365
Oqtane.Client/Modules/Controls/QuillTextEditor.razor
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@implements ITextEditor
|
||||||
|
@inject IStringLocalizer<QuillTextEditor> Localizer
|
||||||
|
|
||||||
|
<div class="quill-text-editor">
|
||||||
|
<TabStrip ActiveTab="@_activetab">
|
||||||
|
@if (AllowRichText)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (AllowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div @ref="@_toolBar">
|
||||||
|
@if (ToolbarContent != null)
|
||||||
|
{
|
||||||
|
@ToolbarContent
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="ql-header">
|
||||||
|
<option selected=""></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
<option value="3"></option>
|
||||||
|
<option value="4"></option>
|
||||||
|
<option value="5"></option>
|
||||||
|
</select>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-bold"></button>
|
||||||
|
<button class="ql-italic"></button>
|
||||||
|
<button class="ql-underline"></button>
|
||||||
|
<button class="ql-strike"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<select class="ql-color"></select>
|
||||||
|
<select class="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-list" value="ordered"></button>
|
||||||
|
<button class="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div @ref="@_editorElement"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
@if (AllowRawHtml)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (AllowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (ReadOnly)
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
</TabStrip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
|
private QuillEditorInterop interop;
|
||||||
|
private FileManager _fileManager;
|
||||||
|
private string _activetab = "Rich";
|
||||||
|
|
||||||
|
private ElementReference _editorElement;
|
||||||
|
private ElementReference _toolBar;
|
||||||
|
private bool _richfilemanager = false;
|
||||||
|
private string _richhtml = string.Empty;
|
||||||
|
private string _originalrichhtml = string.Empty;
|
||||||
|
|
||||||
|
private bool _rawfilemanager = false;
|
||||||
|
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
|
||||||
|
private string _rawhtml = string.Empty;
|
||||||
|
private string _originalrawhtml = string.Empty;
|
||||||
|
|
||||||
|
private string _message = string.Empty;
|
||||||
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
interop = new QuillEditorInterop(JSRuntime);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Placeholder))
|
||||||
|
{
|
||||||
|
Placeholder = Localizer["Placeholder"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (!AllowRichText)
|
||||||
|
{
|
||||||
|
_activetab = "Raw";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
|
||||||
|
if (AllowRichText)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await interop.CreateEditor(
|
||||||
|
_editorElement,
|
||||||
|
_toolBar,
|
||||||
|
ReadOnly,
|
||||||
|
Placeholder,
|
||||||
|
Theme,
|
||||||
|
DebugLevel);
|
||||||
|
|
||||||
|
await interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
|
||||||
|
// preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
|
||||||
|
_originalrichhtml = await interop.GetHtml(_editorElement);
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
if (_contentchanged)
|
||||||
|
{
|
||||||
|
// reload editor if Content passed to component has changed
|
||||||
|
await interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
_originalrichhtml = await interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// preserve changed content on re-render event
|
||||||
|
var richhtml = await interop.GetHtml(_editorElement);
|
||||||
|
if (richhtml != _richhtml)
|
||||||
|
{
|
||||||
|
_richhtml = richhtml;
|
||||||
|
await interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentchanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(string content)
|
||||||
|
{
|
||||||
|
_richhtml = content;
|
||||||
|
_rawhtml = content;
|
||||||
|
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||||
|
_originalrichhtml = "";
|
||||||
|
_richhtml = content;
|
||||||
|
_contentchanged = content != _originalrawhtml;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetContent()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (AllowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRichFileManager()
|
||||||
|
{
|
||||||
|
_richfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRawFileManager()
|
||||||
|
{
|
||||||
|
_rawfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetHtml()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (AllowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertRichImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (_richfilemanager)
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
|
||||||
|
_richhtml = await interop.GetHtml(_editorElement);
|
||||||
|
_richfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_editorIndex = await interop.GetCurrentCursor(_editorElement);
|
||||||
|
_richfilemanager = true;
|
||||||
|
}
|
||||||
|
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(_rawhtmlid);
|
||||||
|
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||||
|
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||||
|
_rawfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rawfilemanager = true;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
@ -1,128 +1,22 @@
|
|||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
|
@using Microsoft.Extensions.DependencyInjection
|
||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="margin-bottom: 50px;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<TabStrip ActiveTab="@_activetab">
|
@_textEditorComponent
|
||||||
@if (AllowRichText)
|
|
||||||
{
|
|
||||||
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
|
||||||
@if (_richfilemanager)
|
|
||||||
{
|
|
||||||
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
@if (AllowFileManagement)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
|
||||||
}
|
|
||||||
@if (_richfilemanager)
|
|
||||||
{
|
|
||||||
@((MarkupString)" ")
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div @ref="@_toolBar">
|
|
||||||
@if (ToolbarContent != null)
|
|
||||||
{
|
|
||||||
@ToolbarContent
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<select class="ql-header">
|
|
||||||
<option selected=""></option>
|
|
||||||
<option value="1"></option>
|
|
||||||
<option value="2"></option>
|
|
||||||
<option value="3"></option>
|
|
||||||
<option value="4"></option>
|
|
||||||
<option value="5"></option>
|
|
||||||
</select>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-bold"></button>
|
|
||||||
<button class="ql-italic"></button>
|
|
||||||
<button class="ql-underline"></button>
|
|
||||||
<button class="ql-strike"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<select class="ql-color"></select>
|
|
||||||
<select class="ql-background"></select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-list" value="ordered"></button>
|
|
||||||
<button class="ql-list" value="bullet"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-link"></button>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div @ref="@_editorElement"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
}
|
|
||||||
@if (AllowRawHtml)
|
|
||||||
{
|
|
||||||
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
|
||||||
@if (_rawfilemanager)
|
|
||||||
{
|
|
||||||
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
@if (AllowFileManagement)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
|
||||||
}
|
|
||||||
@if (_rawfilemanager)
|
|
||||||
{
|
|
||||||
@((MarkupString)" ")
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (ReadOnly)
|
|
||||||
{
|
|
||||||
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
|
||||||
}
|
|
||||||
</TabPanel>
|
|
||||||
}
|
|
||||||
</TabStrip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private ITextEditorProvider _textEditorProvider;
|
||||||
|
private RenderFragment _textEditorComponent;
|
||||||
private RichTextEditorInterop interop;
|
private ITextEditor _textEditor;
|
||||||
private FileManager _fileManager;
|
|
||||||
private string _activetab = "Rich";
|
|
||||||
|
|
||||||
private ElementReference _editorElement;
|
|
||||||
private ElementReference _toolBar;
|
|
||||||
private bool _richfilemanager = false;
|
|
||||||
private string _richhtml = string.Empty;
|
|
||||||
private string _originalrichhtml = string.Empty;
|
|
||||||
|
|
||||||
private bool _rawfilemanager = false;
|
|
||||||
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
|
|
||||||
private string _rawhtml = string.Empty;
|
|
||||||
private string _originalrawhtml = string.Empty;
|
|
||||||
|
|
||||||
private string _message = string.Empty;
|
|
||||||
private bool _contentchanged = false;
|
|
||||||
private int _editorIndex;
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
@ -147,190 +41,114 @@
|
|||||||
public RenderFragment ToolbarContent { get; set; }
|
public RenderFragment ToolbarContent { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Theme { get; set; } = "snow";
|
public string Theme { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string DebugLevel { get; set; } = "info";
|
public string DebugLevel { get; set; }
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
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 }
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
interop = new RichTextEditorInterop(JSRuntime);
|
_textEditorProvider = await GetTextEditorProvider();
|
||||||
if (string.IsNullOrEmpty(Placeholder))
|
|
||||||
{
|
|
||||||
Placeholder = Localizer["Placeholder"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_richhtml = Content;
|
_textEditorComponent = (builder) =>
|
||||||
_rawhtml = Content;
|
|
||||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
|
||||||
_originalrichhtml = "";
|
|
||||||
|
|
||||||
if (Content != _originalrawhtml)
|
|
||||||
{
|
{
|
||||||
_contentchanged = true; // identifies when Content parameter has changed
|
CreateTextEditor(builder);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!AllowRichText)
|
|
||||||
{
|
|
||||||
_activetab = "Raw";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
if(_textEditor != null)
|
||||||
|
|
||||||
if (AllowRichText)
|
|
||||||
{
|
{
|
||||||
if (firstRender)
|
_textEditor.Initialize(Content);
|
||||||
{
|
|
||||||
await interop.CreateEditor(
|
|
||||||
_editorElement,
|
|
||||||
_toolBar,
|
|
||||||
ReadOnly,
|
|
||||||
Placeholder,
|
|
||||||
Theme,
|
|
||||||
DebugLevel);
|
|
||||||
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
|
|
||||||
// preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
|
|
||||||
_originalrichhtml = await interop.GetHtml(_editorElement);
|
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_initialized)
|
|
||||||
{
|
|
||||||
if (_contentchanged)
|
|
||||||
{
|
|
||||||
// reload editor if Content passed to component has changed
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
_originalrichhtml = await interop.GetHtml(_editorElement);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// preserve changed content on re-render event
|
|
||||||
var richhtml = await interop.GetHtml(_editorElement);
|
|
||||||
if (richhtml != _richhtml)
|
|
||||||
{
|
|
||||||
_richhtml = richhtml;
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_contentchanged = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseRichFileManager()
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
{
|
|
||||||
_richfilemanager = false;
|
|
||||||
_message = string.Empty;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseRawFileManager()
|
|
||||||
{
|
|
||||||
_rawfilemanager = false;
|
|
||||||
_message = string.Empty;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetHtml()
|
public async Task<string> GetHtml()
|
||||||
{
|
{
|
||||||
// evaluate raw html content as first priority
|
return await _textEditor.GetContent();
|
||||||
if (_rawhtml != _originalrawhtml)
|
}
|
||||||
{
|
|
||||||
return _rawhtml;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var richhtml = "";
|
|
||||||
|
|
||||||
if (AllowRichText)
|
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
if(_textEditorProvider != null)
|
||||||
|
{
|
||||||
|
var editorType = Type.GetType(_textEditorProvider.EditorType);
|
||||||
|
if (editorType != null)
|
||||||
{
|
{
|
||||||
richhtml = await interop.GetHtml(_editorElement);
|
builder.OpenComponent(0, editorType);
|
||||||
}
|
|
||||||
|
|
||||||
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
//set editor parameters if available.
|
||||||
{
|
var attributes = new Dictionary<string, object>
|
||||||
// convert Quill's empty content to empty string
|
|
||||||
if (richhtml == "<p><br></p>")
|
|
||||||
{
|
{
|
||||||
richhtml = string.Empty;
|
{ "AllowFileManagement", AllowFileManagement },
|
||||||
|
{ "AllowRichText", AllowRichText },
|
||||||
|
{ "AllowRawHtml", AllowRawHtml },
|
||||||
|
{ "ReadOnly", ReadOnly }
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(Theme))
|
||||||
|
{
|
||||||
|
attributes.Add("Theme", Theme);
|
||||||
}
|
}
|
||||||
return richhtml;
|
if (!string.IsNullOrEmpty(DebugLevel))
|
||||||
}
|
{
|
||||||
else
|
attributes.Add("DebugLevel", DebugLevel);
|
||||||
{
|
}
|
||||||
// return original raw html content
|
if (!string.IsNullOrEmpty(Placeholder))
|
||||||
return _originalrawhtml;
|
{
|
||||||
|
attributes.Add("Placeholder", Placeholder);
|
||||||
|
}
|
||||||
|
if(ToolbarContent != null)
|
||||||
|
{
|
||||||
|
attributes.Add("ToolbarContent", ToolbarContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = 1;
|
||||||
|
foreach(var name in attributes.Keys)
|
||||||
|
{
|
||||||
|
if (editorType.GetProperty(name) != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(index++, name, attributes[name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddComponentReferenceCapture(index, (c) =>
|
||||||
|
{
|
||||||
|
_textEditor = (ITextEditor)c;
|
||||||
|
});
|
||||||
|
builder.CloseComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertRichImage()
|
private async Task<ITextEditorProvider> GetTextEditorProvider()
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
const string DefaultEditorName = "Quill";
|
||||||
if (_richfilemanager)
|
|
||||||
{
|
|
||||||
var file = _fileManager.GetFile();
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
|
|
||||||
_richhtml = await interop.GetHtml(_editorElement);
|
|
||||||
_richfilemanager = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_message = Localizer["Message.Require.Image"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_editorIndex = await interop.GetCurrentCursor(_editorElement);
|
|
||||||
_richfilemanager = true;
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InsertRawImage()
|
var editorName = await GetTextEditorName(DefaultEditorName);
|
||||||
{
|
var editorProviders = ServiceProvider.GetServices<ITextEditorProvider>();
|
||||||
_message = string.Empty;
|
var editorProvider = editorProviders.FirstOrDefault(i => i.Name == editorName);
|
||||||
if (_rawfilemanager)
|
if(editorProvider == null)
|
||||||
{
|
{
|
||||||
var file = _fileManager.GetFile();
|
editorProvider = editorProviders.FirstOrDefault(i => i.Name == DefaultEditorName);
|
||||||
if (file != null)
|
}
|
||||||
{
|
|
||||||
var interop = new Interop(JSRuntime);
|
return editorProvider;
|
||||||
int pos = await interop.GetCaretPosition(_rawhtmlid);
|
}
|
||||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
|
||||||
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
private async Task<string> GetTextEditorName(string defaultName)
|
||||||
_rawfilemanager = false;
|
{
|
||||||
}
|
const string EditorSettingName = "TextEditor";
|
||||||
else
|
|
||||||
{
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_message = Localizer["Message.Require.Image"];
|
return SettingService.GetSetting(settings, EditorSettingName, defaultName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rawfilemanager = true;
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
11
Oqtane.Client/Providers/QuillTextEditorProvider.cs
Normal file
11
Oqtane.Client/Providers/QuillTextEditorProvider.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using Oqtane.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Providers
|
||||||
|
{
|
||||||
|
public class QuillTextEditorProvider : ITextEditorProvider
|
||||||
|
{
|
||||||
|
public string Name => "Quill";
|
||||||
|
|
||||||
|
public string EditorType => "Oqtane.Modules.Controls.QuillTextEditor, Oqtane.Client";
|
||||||
|
}
|
||||||
|
}
|
@ -117,12 +117,12 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="InsertImage" xml:space="preserve">
|
|
||||||
<value>Insert Image</value>
|
|
||||||
</data>
|
|
||||||
<data name="Close" xml:space="preserve">
|
<data name="Close" xml:space="preserve">
|
||||||
<value>Close</value>
|
<value>Close</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InsertImage" xml:space="preserve">
|
||||||
|
<value>Insert Image</value>
|
||||||
|
</data>
|
||||||
<data name="Message.Require.Image" xml:space="preserve">
|
<data name="Message.Require.Image" xml:space="preserve">
|
||||||
<value>You Must Select An Image To Insert</value>
|
<value>You Must Select An Image To Insert</value>
|
||||||
</data>
|
</data>
|
@ -19,6 +19,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Infrastructure.Interfaces;
|
using Oqtane.Infrastructure.Interfaces;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Managers;
|
using Oqtane.Managers;
|
||||||
using Oqtane.Modules;
|
using Oqtane.Modules;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
@ -102,6 +103,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ISearchService, SearchService>();
|
services.AddScoped<ISearchService, SearchService>();
|
||||||
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
|
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
|
||||||
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +150,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddTransient<IUpgradeManager, UpgradeManager>();
|
services.AddTransient<IUpgradeManager, UpgradeManager>();
|
||||||
services.AddTransient<IUserManager, UserManager>();
|
services.AddTransient<IUserManager, UserManager>();
|
||||||
|
|
||||||
|
// providers
|
||||||
|
services.AddTransient<ITextEditorProvider, QuillTextEditorProvider>();
|
||||||
|
|
||||||
// obsolete - replaced by ITenantManager
|
// obsolete - replaced by ITenantManager
|
||||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||||
|
|
||||||
|
22
Oqtane.Shared/Interfaces/ITextEditor.cs
Normal file
22
Oqtane.Shared/Interfaces/ITextEditor.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Text editor interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITextEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// initializes the editor with the initialize content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">the initialize content.</param>
|
||||||
|
void Initialize(string content);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get content from the editor.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<string> GetContent();
|
||||||
|
}
|
||||||
|
}
|
18
Oqtane.Shared/Interfaces/ITextEditorProvider.cs
Normal file
18
Oqtane.Shared/Interfaces/ITextEditorProvider.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Oqtane.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rich text editor provider interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITextEditorProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The text editor provider name.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text editor type full name.
|
||||||
|
/// </summary>
|
||||||
|
string EditorType { get; }
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user