487 lines
19 KiB
Plaintext
487 lines
19 KiB
Plaintext
@namespace Oqtane.Modules.Controls
|
|
@inherits ModuleControlBase
|
|
@implements ITextEditor
|
|
@inject ISettingService SettingService
|
|
@inject NavigationManager NavigationManager
|
|
@inject IStringLocalizer<QuillJSTextEditor> Localizer
|
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
|
|
|
<div class="quill-text-editor @(_settingsVisible ? "d-none" : "d-block")">
|
|
<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 (!string.IsNullOrEmpty(_toolbarContent))
|
|
{
|
|
@((MarkupString)_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>
|
|
}
|
|
@if (_hasAdminPermission)
|
|
{
|
|
<span class="ql-formats">
|
|
<button class="ql-settings" @onclick="OpenSettings"></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>
|
|
}
|
|
else if(_hasAdminPermission)
|
|
{
|
|
<button type="button" class="btn btn-primary ms-1" @onclick="OpenSettings">@Localizer["Settings"]</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 class="quill-text-editor-settings @(_settingsVisible ? "d-block" : "d-none")">
|
|
<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="_allowFileManagementSetting" />
|
|
</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="_allowRawHtmlSetting" />
|
|
</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="_allowRichTextSetting" />
|
|
</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="_themeSetting" />
|
|
</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="_debugLevelSetting">
|
|
@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="_toolbarContentSetting" rows="5" />
|
|
</div>
|
|
</div>
|
|
<div class="row mb-1 align-items-center">
|
|
<div class="col-sm-9 offset-sm-3">
|
|
<button type="button" class="btn btn-success" @onclick="@(async () => await UpdateSettings())">@SharedLocalizer["Save"]</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
|
|
|
private bool _initialized = false;
|
|
|
|
private QuillEditorInterop interop;
|
|
private FileManager _fileManager;
|
|
private string _activetab = "Rich";
|
|
private bool _hasAdminPermission;
|
|
|
|
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;
|
|
|
|
//adjust settings variables won't affect UI
|
|
private bool _allowFileManagementSetting = false;
|
|
private bool _allowRawHtmlSetting = false;
|
|
private bool _allowRichTextSetting = false;
|
|
private string _themeSetting = "snow";
|
|
private string _debugLevelSetting = "info";
|
|
private string _toolbarContentSetting = string.Empty;
|
|
private bool _settingsVisible;
|
|
|
|
private bool _settingsLoaded;
|
|
|
|
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;
|
|
|
|
private List<string> _debugLevels = new List<string> { "info", "log", "warn", "error" };
|
|
|
|
[Parameter]
|
|
public bool ReadOnly { get; set; }
|
|
|
|
[Parameter]
|
|
public string Placeholder { 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 async Task OnInitializedAsync()
|
|
{
|
|
interop = new QuillEditorInterop(JSRuntime);
|
|
|
|
if (string.IsNullOrEmpty(Placeholder))
|
|
{
|
|
Placeholder = Localizer["Placeholder"];
|
|
}
|
|
|
|
await LoadSettings();
|
|
}
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
if (!_allowRichText)
|
|
{
|
|
_activetab = "Raw";
|
|
}
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
|
|
if (_allowRichText)
|
|
{
|
|
if (_settingsLoaded && !_initialized)
|
|
{
|
|
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);
|
|
|
|
_contentchanged = false;
|
|
}
|
|
else
|
|
{
|
|
// preserve changed content on re-render event
|
|
var richhtml = await interop.GetHtml(_editorElement);
|
|
if (richhtml != _richhtml)
|
|
{
|
|
_richhtml = richhtml;
|
|
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Initialize(string content)
|
|
{
|
|
_richhtml = content;
|
|
_rawhtml = content;
|
|
_originalrichhtml = "";
|
|
_richhtml = content;
|
|
if (!_contentchanged)
|
|
{
|
|
_contentchanged = content != _originalrawhtml;
|
|
}
|
|
|
|
_originalrawhtml = _rawhtml; // preserve for comparison later
|
|
|
|
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();
|
|
}
|
|
|
|
private void OpenSettings()
|
|
{
|
|
_settingsVisible = true;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task LoadSettings()
|
|
{
|
|
try
|
|
{
|
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
|
_allowFileManagementSetting = _allowFileManagement = SettingService.GetSetting(settings, "QuillTextEditor_AllowFileManagement", "true") == "true";
|
|
_allowRawHtmlSetting = _allowRawHtml = SettingService.GetSetting(settings, "QuillTextEditor_AllowRawHtml", "true") == "true";
|
|
_allowRichTextSetting = _allowRichText = SettingService.GetSetting(settings, "QuillTextEditor_AllowRichText", "true") == "true";
|
|
_themeSetting = _theme = SettingService.GetSetting(settings, "QuillTextEditor_Theme", "snow");
|
|
_debugLevelSetting = _debugLevel = SettingService.GetSetting(settings, "QuillTextEditor_DebugLevel", "info");
|
|
_toolbarContentSetting = _toolbarContent = SettingService.GetSetting(settings, "QuillTextEditor_ToolbarContent", string.Empty);
|
|
|
|
_hasAdminPermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
|
|
|
_settingsLoaded = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddModuleMessage(ex.Message, MessageType.Error);
|
|
}
|
|
}
|
|
|
|
private async Task UpdateSettings()
|
|
{
|
|
try
|
|
{
|
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting.ToString().ToLower());
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting.ToString().ToLower());
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting.ToString().ToLower());
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
|
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
|
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
|
|
|
NavigationManager.NavigateTo(NavigationManager.Uri);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddModuleMessage(ex.Message, MessageType.Error);
|
|
}
|
|
}
|
|
}
|