Fix #4316: move the raw html editor into quill editor instance.

This commit is contained in:
Ben
2024-06-07 08:35:42 +08:00
parent c125a7fe07
commit b00c2afc46
5 changed files with 293 additions and 363 deletions

View File

@ -4,80 +4,132 @@
@inject IStringLocalizer<QuillTextEditor> Localizer
<div class="quill-text-editor">
@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)
<TabStrip ActiveTab="@_activetab">
@if (AllowRichText)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<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)
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
{
@ToolbarContent
<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)"&nbsp;&nbsp;")
<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)"&nbsp;&nbsp;")
<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
{
<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>
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</div>
<div @ref="@_editorElement"></div>
</div>
</div>
</TabPanel>
}
</TabStrip>
</div>
@code {
private bool _richfilemanager = false;
private bool _initialized = false;
private QuillEditorInterop interop;
private FileManager _fileManager;
private string _message = string.Empty;
private string _activetab = "Rich";
private ElementReference _editorElement;
private ElementReference _toolBar;
private QuillEditorInterop interop;
private int _editorIndex;
private bool _richfilemanager = false;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _initialized = false;
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; }
@ -85,10 +137,10 @@
public string Placeholder { get; set; }
[Parameter]
public string Theme { get; set; }
public string Theme { get; set; } = "snow";
[Parameter]
public string DebugLevel { get; set; }
public string DebugLevel { get; set; } = "info";
[Parameter]
public RenderFragment ToolbarContent { get; set; }
@ -103,59 +155,161 @@
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 (firstRender)
if (AllowRichText)
{
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 (firstRender)
{
if (_contentchanged)
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)
{
// 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)
if (_contentchanged)
{
_richhtml = richhtml;
// 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;
_contentchanged = false;
}
}
public void Initialize(string content, bool updated)
public void Initialize(string content)
{
_richhtml = content;
_contentchanged = updated;
_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()
@ -183,28 +337,29 @@
StateHasChanged();
}
public void CloseRichFileManager()
public async Task InsertRawImage()
{
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public async Task<string> GetContent()
{
var richhtml = await interop.GetHtml(_editorElement);
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
if (_rawfilemanager)
{
// convert Quill's empty content to empty string
if (richhtml == "<p><br></p>")
var file = _fileManager.GetFile();
if (file != null)
{
richhtml = string.Empty;
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"];
}
return richhtml;
}
else
{
return null;
_rawfilemanager = true;
}
StateHasChanged();
}
}

View File

@ -9,57 +9,11 @@
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip ActiveTab="@_activetab">
@if (AllowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@_textEditorComponent
</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)"&nbsp;&nbsp;")
<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>
@_textEditorComponent
</div>
</div>
@code {
private string _activetab = "Rich";
private bool _rawfilemanager = false;
private FileManager _fileManager;
private string _message = string.Empty;
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private ITextEditorProvider _textEditorProvider;
private RenderFragment _textEditorComponent;
private ITextEditor _textEditor;
@ -87,36 +41,20 @@
public RenderFragment ToolbarContent { get; set; }
[Parameter]
public string Theme { get; set; } = "snow";
public string Theme { get; set; }
[Parameter]
public string DebugLevel { get; set; } = "info";
public string DebugLevel { get; set; }
public override List<Resource> Resources { get; set; } = new List<Resource>();
protected override async Task OnInitializedAsync()
{
if (string.IsNullOrEmpty(Placeholder))
{
Placeholder = Localizer["Placeholder"];
}
if(AllowRichText)
{
_textEditorProvider = await GetTextEditorProvider();
}
_textEditorProvider = await GetTextEditorProvider();
}
protected override void OnParametersSet()
{
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
if (!AllowRichText)
{
_activetab = "Raw";
}
_textEditorComponent = (builder) =>
{
CreateTextEditor(builder);
@ -127,64 +65,15 @@
{
if(_textEditor != null)
{
_textEditor.Initialize(Content, Content != _originalrawhtml);
_textEditor.Initialize(Content);
}
await base.OnAfterRenderAsync(firstRender);
}
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 = string.Empty;
if (AllowRichText && _textEditor != null)
{
richhtml = await _textEditor.GetContent();
}
return richhtml != null ? richhtml : _originalrawhtml;
}
}
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();
return await _textEditor.GetContent();
}
private void CreateTextEditor(RenderTreeBuilder builder)
@ -200,13 +89,28 @@
var attributes = new Dictionary<string, object>
{
{ "AllowFileManagement", AllowFileManagement },
{ "ReadOnly", ReadOnly },
{ "Placeholder", Placeholder },
{ "Theme", Theme },
{ "DebugLevel", DebugLevel },
{ "ToolbarContent", ToolbarContent }
{ "AllowRichText", AllowRichText },
{ "AllowRawHtml", AllowRawHtml },
{ "ReadOnly", ReadOnly }
};
if(!string.IsNullOrEmpty(Theme))
{
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);
}
var index = 1;
foreach(var name in attributes.Keys)
{