migrated quill to jodit(links not working)

This commit is contained in:
Aleh Khantsevich
2024-07-01 00:57:07 +02:00
parent bfc2af71a4
commit 963a15abe7
13 changed files with 5905 additions and 1194 deletions
@@ -8,7 +8,7 @@ namespace Wino.Core.Domain.Interfaces
{ {
string GetWebAuthenticationBrokerUri(); string GetWebAuthenticationBrokerUri();
Task<string> GetMimeMessageStoragePath(); Task<string> GetMimeMessageStoragePath();
Task<string> GetQuillEditorBundlePathAsync(); Task<string> GetEditorBundlePathAsync();
Task LaunchFileAsync(string filePath); Task LaunchFileAsync(string filePath);
Task LaunchUriAsync(Uri uri); Task LaunchUriAsync(Uri uri);
bool IsAppRunning(); bool IsAppRunning();
@@ -0,0 +1,9 @@
namespace Wino.Core.Domain.Models.Requests
{
// Used to pass messages from the webview to the app.
public class WebViewMessage
{
public string type { get; set; }
public string value { get; set; }
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ namespace Wino.Services
return new GoogleAuthorizationRequest(state, code_verifier, code_challenge); return new GoogleAuthorizationRequest(state, code_verifier, code_challenge);
} }
public async Task<string> GetQuillEditorBundlePathAsync() public async Task<string> GetEditorBundlePathAsync()
{ {
if (string.IsNullOrEmpty(_editorBundlePath)) if (string.IsNullOrEmpty(_editorBundlePath))
{ {
+59 -50
View File
@@ -10,6 +10,7 @@ using Windows.UI.Xaml.Controls;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Views.Settings; using Wino.Views.Settings;
namespace Wino.Dialogs namespace Wino.Dialogs
@@ -71,9 +72,9 @@ namespace Wino.Dialogs
_getHTMLBodyFunction = new Func<Task<string>>(async () => _getHTMLBodyFunction = new Func<Task<string>>(async () =>
{ {
var quillContent = await InvokeScriptSafeAsync("GetHTMLContent();"); var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
return JsonConvert.DeserializeObject<string>(quillContent); return JsonConvert.DeserializeObject<string>(editorContent);
}); });
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>(); var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
@@ -131,42 +132,42 @@ namespace Wino.Dialogs
private async void BoldButtonClicked(object sender, RoutedEventArgs e) private async void BoldButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('boldButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('bold')");
} }
private async void ItalicButtonClicked(object sender, RoutedEventArgs e) private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('italicButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('italic')");
} }
private async void UnderlineButtonClicked(object sender, RoutedEventArgs e) private async void UnderlineButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('underlineButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('underline')");
} }
private async void StrokeButtonClicked(object sender, RoutedEventArgs e) private async void StrokeButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('strikeButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('strikethrough')");
} }
private async void BulletListButtonClicked(object sender, RoutedEventArgs e) private async void BulletListButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('bulletListButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('insertunorderedlist')");
} }
private async void OrderedListButtonClicked(object sender, RoutedEventArgs e) private async void OrderedListButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('orderedListButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('insertorderedlist')");
} }
private async void IncreaseIndentClicked(object sender, RoutedEventArgs e) private async void IncreaseIndentClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('increaseIndentButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('indent')");
} }
private async void DecreaseIndentClicked(object sender, RoutedEventArgs e) private async void DecreaseIndentClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('decreaseIndentButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('outdent')");
} }
private async void DirectionButtonClicked(object sender, RoutedEventArgs e) private async void DirectionButtonClicked(object sender, RoutedEventArgs e)
@@ -182,16 +183,16 @@ namespace Wino.Dialogs
switch (alignment) switch (alignment)
{ {
case "left": case "left":
await InvokeScriptSafeAsync("document.getElementById('ql-align-left').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyleft')");
break; break;
case "center": case "center":
await InvokeScriptSafeAsync("document.getElementById('ql-align-center').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifycenter')");
break; break;
case "right": case "right":
await InvokeScriptSafeAsync("document.getElementById('ql-align-right').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyright')");
break; break;
case "justify": case "justify":
await InvokeScriptSafeAsync("document.getElementById('ql-align-justify').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyfull')");
break; break;
} }
} }
@@ -218,19 +219,22 @@ namespace Wino.Dialogs
{ {
return await Chromium.ExecuteScriptAsync(function); return await Chromium.ExecuteScriptAsync(function);
} }
catch { } catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return string.Empty; return string.Empty;
} }
private async void AddImageClicked(object sender, RoutedEventArgs e) private async void AddImageClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('addImageButton').click();"); await InvokeScriptSafeAsync("imageInput.click();");
} }
private async Task FocusEditorAsync() private async Task FocusEditorAsync()
{ {
await InvokeScriptSafeAsync("quill.focus();"); await InvokeScriptSafeAsync("editor.selection.focus();");
Chromium.Focus(FocusState.Keyboard); Chromium.Focus(FocusState.Keyboard);
Chromium.Focus(FocusState.Programmatic); Chromium.Focus(FocusState.Programmatic);
@@ -256,7 +260,6 @@ namespace Wino.Dialogs
private async void LinkButtonClicked(object sender, RoutedEventArgs e) private async void LinkButtonClicked(object sender, RoutedEventArgs e)
{ {
// Get selected text from Quill.
HyperlinkTextBox.Text = await TryGetSelectedTextAsync(); HyperlinkTextBox.Text = await TryGetSelectedTextAsync();
HyperlinkFlyout.ShowAt(LinkButton); HyperlinkFlyout.ShowAt(LinkButton);
@@ -298,7 +301,7 @@ namespace Wino.Dialogs
private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args) private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args)
{ {
var editorBundlePath = (await _nativeAppService.GetQuillEditorBundlePathAsync()).Replace("editor.html", string.Empty); var editorBundlePath = (await _nativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow); Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.Source = new Uri("https://app.editor/editor.html"); Chromium.Source = new Uri("https://app.editor/editor.html");
@@ -320,46 +323,52 @@ namespace Wino.Dialogs
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args) private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{ {
var change = JsonConvert.DeserializeObject<string>(args.WebMessageAsJson); var change = JsonConvert.DeserializeObject<WebViewMessage>(args.WebMessageAsJson);
bool isEnabled = change.EndsWith("ql-active"); if (change.type == "bold")
if (change.StartsWith("ql-bold"))
BoldButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-italic"))
ItalicButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-underline"))
UnderlineButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-strike"))
StrokeButton.IsChecked = isEnabled;
else if (change.StartsWith("orderedListButton"))
OrderedListButton.IsChecked = isEnabled;
else if (change.StartsWith("bulletListButton"))
BulletListButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-direction"))
DirectionButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-align-left"))
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; BoldButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 0;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-center")) else if (change.type == "italic")
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; ItalicButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 1;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-right")) else if (change.type == "underline")
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; UnderlineButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 2;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-justify")) else if (change.type == "strikethrough")
{ {
StrokeButton.IsChecked = change.value == "true";
}
else if (change.type == "ol")
{
OrderedListButton.IsChecked = change.value == "true";
}
else if (change.type == "ul")
{
BulletListButton.IsChecked = change.value == "true";
}
else if (change.type == "indent")
{
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
}
else if (change.type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
}
else if (change.type == "alignment")
{
var parsedValue = change.value switch
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,
"jodit-icon_right" => 2,
"jodit-icon_justify" => 3,
_ => 0
};
AlignmentListView.SelectionChanged -= AlignmentChanged; AlignmentListView.SelectionChanged -= AlignmentChanged;
AlignmentListView.SelectedIndex = 3; AlignmentListView.SelectedIndex = parsedValue;
AlignmentListView.SelectionChanged += AlignmentChanged; AlignmentListView.SelectionChanged += AlignmentChanged;
} }
} }
+20 -53
View File
@@ -4,72 +4,39 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="language" content="english"> <meta name="language" content="english">
<script src="./libs/quill.js"></script> <script src="./libs/jodit.min.js"></script>
<script src="./libs/darkreader.js"></script> <script src="./libs/darkreader.js"></script>
<link rel="stylesheet" href="./libs/quill.snow.css" /> <link rel="stylesheet" href="./libs/jodit.min.css" />
<link rel="stylesheet" href="./global.css" /> <link rel="stylesheet" href="./global.css" />
<style> <style>
#editor { #editor {
height: 100%; height: 100%;
} }
.jodit-container:not(.jodit_inline) {
background-color: transparent;
border: none;
border-radius: initial;
}
/* Hide taskbar in css. Should not be hidden from configuration, because it's used to sync state with native buttons. */
.jodit-toolbar__box {
/* display: none; */
}
body {
margin: 8px;
}
</style> </style>
</head> </head>
<body> <body>
<meta name="color-scheme" content="dark light"> <meta name="color-scheme" content="dark light">
<!-- Hidden toolbar. It used to provide events to the host --> <textarea id="editor" name="editor"></textarea>
<div id="toolbar-container" style="display: none;">
<span class="ql-formats">
<select class="ql-font"></select>
<select class="ql-size"></select>
</span>
<span class="ql-formats">
<button id="boldButton" class="ql-bold"></button>
<button id="italicButton" class="ql-italic"></button>
<button id="underlineButton" class="ql-underline"></button>
<button id="strikeButton" 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-script" value="sub"></button>
<button class="ql-script" value="super"></button>
</span>
<span class="ql-formats">
<button class="ql-header" value="1"></button>
<button class="ql-header" value="2"></button>
<button class="ql-blockquote"></button>
<button class="ql-code-block"></button>
</span>
<span class="ql-formats">
<button id="orderedListButton" class="ql-list" value="ordered"></button>
<button id="bulletListButton" class="ql-list" value="bullet"></button>
<button id="decreaseIndentButton" class="ql-indent" value="-1"></button>
<button id="increaseIndentButton" class="ql-indent" value="+1"></button>
</span>
<span class="ql-formats">
<button id="directionButton" class="ql-direction" value="rtl"></button>
<button id="ql-align-left" class="ql-align" value=""></button>
<button id="ql-align-center" class="ql-align" value="center"></button>
<button id="ql-align-right" class="ql-align" value="right"></button>
<button id="ql-align-justify" class="ql-align" value="justify"></button>
<!--<select id="alignmentButton" class="ql-align"></select>-->
</span>
<span class="ql-formats">
<button class="ql-link"></button>
<button id=addImageButton class="ql-image"></button>
<button class="ql-video"></button>
<button class="ql-formula"></button>
</span>
<span class="ql-formats">
<button class="ql-clean"></button>
</span>
</div>
<div id="editor"></div> <!-- hidden input to handle image uploads -->
<input type="file" id="imageInput" style="display:none;">
<script src="/editor.js"></script> <script src="/editor.js"></script>
</body> </body>
+75 -81
View File
@@ -1,85 +1,79 @@
const quill = new Quill('#editor', { const editor = Jodit.make("#editor", {
modules: { "useSearch": false,
toolbar: '#toolbar-container' "toolbar": true,
}, "buttons": "bold,italic,underline,strikethrough,eraser,ul,ol,font,fontsize,paragraph,indent,outdent,left,brush",
placeholder: '', "inline": true,
theme: 'snow' "toolbarInlineForSelection": false,
"showCharsCounter": false,
"showWordsCounter": false,
"showXPathInStatusbar": false,
"disablePlugins": "add-new-line",
"showPlaceholder": false,
"uploader": {
"insertImageAsBase64URI": true
}
}); });
var boldButton = document.getElementById('boldButton'); // Handle the image input change event
var italicButton = document.getElementById('italicButton'); imageInput.addEventListener('change', () => {
var underlineButton = document.getElementById('underlineButton'); const file = imageInput.files[0];
var strikeButton = document.getElementById('strikeButton'); if (file) {
const reader = new FileReader();
reader.onload = function (event) {
const base64Image = event.target.result;
editor.selection.insertHTML(`<img src="${base64Image}" alt="Embedded Image">`);
};
reader.readAsDataURL(file);
}
});
var orderedListButton = document.getElementById('orderedListButton'); const disabledButtons = ["indent", "outdent"];
var bulletListButton = document.getElementById('bulletListButton'); const ariaPressedButtons = ["bold", "italic", "underline", "strikethrough", "ul", "ol"];
var directionButton = document.getElementById('directionButton'); const alignmentButton = document.querySelector(`[ref='left']`).firstChild.firstChild;
const alignmentObserver = new MutationObserver(function () {
const value = alignmentButton.firstChild.getAttribute('class').split(' ')[0];
window.chrome.webview.postMessage({ type: 'alignment', value: value });
});
alignmentObserver.observe(alignmentButton, { childList: true, attributes: true, attributeFilter: ["class"] });
var alignLeftButton = document.getElementById('ql-align-left'); const ariaObservers = ariaPressedButtons.map(button => {
var alignCenterButton = document.getElementById('ql-align-center'); const buttonContainer = document.querySelector(`[ref='${button}']`);
var alignRightButton = document.getElementById('ql-align-right'); const observer = new MutationObserver(function () { pressedChanged(buttonContainer) });
var alignJustifyButton = document.getElementById('ql-align-justify'); observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["aria-pressed"] });
// The mutation observer return observer;
var boldObserver = new MutationObserver(function () { classChanged(boldButton); }); });
boldObserver.observe(boldButton, { attributes: true, attributeFilter: ["class"] });
var italicObserver = new MutationObserver(function () { classChanged(italicButton); }); const disabledObservers = disabledButtons.map(button => {
italicObserver.observe(italicButton, { attributes: true, attributeFilter: ["class"] }); const buttonContainer = document.querySelector(`[ref='${button}']`);
const observer = new MutationObserver(function () { disabledButtonChanged(buttonContainer) });
observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["disabled"] });
var underlineObserver = new MutationObserver(function () { classChanged(underlineButton); }); return observer;
underlineObserver.observe(underlineButton, { attributes: true, attributeFilter: ["class"] }); });
var strikeObserver = new MutationObserver(function () { classChanged(strikeButton); }); function pressedChanged(buttonContainer) {
strikeObserver.observe(strikeButton, { attributes: true, attributeFilter: ["class"] }); const ref = buttonContainer.getAttribute('ref');
const value = buttonContainer.firstChild.getAttribute('aria-pressed');
var orderedListObserver = new MutationObserver(function () { classAndValueChanged(orderedListButton); }); window.chrome.webview.postMessage({ type: ref, value: value });
orderedListObserver.observe(orderedListButton, { attributes: true, attributeFilter: ["class"] });
var bulletListObserver = new MutationObserver(function () { classAndValueChanged(bulletListButton); });
bulletListObserver.observe(bulletListButton, { attributes: true, attributeFilter: ["class"] });
var directionObserver = new MutationObserver(function () { classChanged(directionButton); });
directionObserver.observe(directionButton, { attributes: true, attributeFilter: ["class"] });
var alignmentObserver = new MutationObserver(function () { alignmentDataValueChanged(alignLeftButton); });
alignmentObserver.observe(alignLeftButton, { attributes: true, attributeFilter: ["class"] });
var alignmentObserverCenter = new MutationObserver(function () { alignmentDataValueChanged(alignCenterButton); });
alignmentObserverCenter.observe(alignCenterButton, { attributes: true, attributeFilter: ["class"] });
var alignmentObserverRight = new MutationObserver(function () { alignmentDataValueChanged(alignRightButton); });
alignmentObserverRight.observe(alignRightButton, { attributes: true, attributeFilter: ["class"] });
var alignmentObserverJustify = new MutationObserver(function () { alignmentDataValueChanged(alignJustifyButton); });
alignmentObserverJustify.observe(alignJustifyButton, { attributes: true, attributeFilter: ["class"] });
function classChanged(button) {
window.chrome.webview.postMessage(`${button.className}`);
} }
function classAndValueChanged(button) { function disabledButtonChanged(buttonContainer) {
window.chrome.webview.postMessage(`${button.id} ${button.className}`); const ref = buttonContainer.getAttribute('ref');
const value = buttonContainer.firstChild.getAttribute('disabled');
console.log(buttonContainer, ref, value);
window.chrome.webview.postMessage({ type: ref, value: value });
} }
function alignmentDataValueChanged(button) {
if (button.className.endsWith('ql-active'))
window.chrome.webview.postMessage(`${button.id}`);
}
function RenderHTML(htmlString) { function RenderHTML(htmlString) {
const delta = quill.clipboard.convert({html: htmlString}) editor.s.insertHTML(htmlString);
editor.synchronizeValues();
quill.setContents(delta, 'silent');
} }
function GetHTMLContent() { function GetHTMLContent() {
return quill.root.innerHTML; return editor.value;
}
function GetTextContent() {
return quill.getText();
} }
function SetLightEditor() { function SetLightEditor() {
@@ -90,23 +84,23 @@ function SetDarkEditor() {
DarkReader.enable(); DarkReader.enable();
} }
function getSelectedText() { //function getSelectedText() {
var range = quill.getSelection(); // var range = quill.getSelection();
if (range) { // if (range) {
if (range.length == 0) { // if (range.length == 0) {
} // }
else { // else {
return quill.getText(range.index, range.length); // return quill.getText(range.index, range.length);
} // }
} // }
} //}
function addHyperlink(url) { //function addHyperlink(url) {
var range = quill.getSelection(); // var range = quill.getSelection();
if (range) { // if (range) {
quill.formatText(range.index, range.length, 'link', url); // quill.formatText(range.index, range.length, 'link', url);
quill.setSelection(0, 0); // quill.setSelection(0, 0);
} // }
} //}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-947
View File
@@ -1,947 +0,0 @@
/*!
* Quill Editor v1.3.6
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-container {
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
height: 1000px !important;
resize: vertical;
margin: 0px;
}
.ql-container.ql-disabled .ql-tooltip {
visibility: hidden;
}
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
pointer-events: none;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
line-height: 1.40;
height: 100%;
outline: none;
overflow-y: auto;
padding: 4px 4px;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
/* Reply Border */
.ql-container.ql-snow {
/* border: solid #636e72; */
}
.ql-editor > * {
cursor: text;
}
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor pre,
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6 {
margin: 0;
padding: 0;
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol,
.ql-editor ul {
padding-left: 1.5em;
}
.ql-editor ol > li,
.ql-editor ul > li {
list-style-type: none;
}
.ql-editor ul > li::before {
content: '\2022';
}
.ql-editor ul[data-checked=true],
.ql-editor ul[data-checked=false] {
pointer-events: none;
}
.ql-editor ul[data-checked=true] > li *,
.ql-editor ul[data-checked=false] > li * {
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before,
.ql-editor ul[data-checked=false] > li::before {
color: #777;
cursor: pointer;
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before {
content: '\2611';
}
.ql-editor ul[data-checked=false] > li::before {
content: '\2610';
}
.ql-editor li::before {
display: inline-block;
white-space: nowrap;
width: 1.2em;
}
.ql-editor li:not(.ql-direction-rtl)::before {
margin-left: -1.5em;
margin-right: 0.3em;
text-align: right;
}
.ql-editor li.ql-direction-rtl::before {
margin-left: 0.3em;
margin-right: -1.5em;
}
.ql-editor ol li:not(.ql-direction-rtl),
.ql-editor ul li:not(.ql-direction-rtl) {
padding-left: 1.5em;
}
.ql-editor ol li.ql-direction-rtl,
.ql-editor ul li.ql-direction-rtl {
padding-right: 1.5em;
}
.ql-editor ol li {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor ol li:before {
content: counter(list-0, decimal) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-increment: list-1;
}
.ql-editor ol li.ql-indent-1:before {
content: counter(list-1, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-2 {
counter-increment: list-2;
}
.ql-editor ol li.ql-indent-2:before {
content: counter(list-2, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-3 {
counter-increment: list-3;
}
.ql-editor ol li.ql-indent-3:before {
content: counter(list-3, decimal) '. ';
}
.ql-editor ol li.ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-4 {
counter-increment: list-4;
}
.ql-editor ol li.ql-indent-4:before {
content: counter(list-4, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-5 {
counter-increment: list-5;
}
.ql-editor ol li.ql-indent-5:before {
content: counter(list-5, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-6 {
counter-increment: list-6;
}
.ql-editor ol li.ql-indent-6:before {
content: counter(list-6, decimal) '. ';
}
.ql-editor ol li.ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-7 {
counter-increment: list-7;
}
.ql-editor ol li.ql-indent-7:before {
content: counter(list-7, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor ol li.ql-indent-8 {
counter-increment: list-8;
}
.ql-editor ol li.ql-indent-8:before {
content: counter(list-8, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-8 {
counter-reset: list-9;
}
.ql-editor ol li.ql-indent-9 {
counter-increment: list-9;
}
.ql-editor ol li.ql-indent-9:before {
content: counter(list-9, decimal) '. ';
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 3em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 4.5em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 3em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 4.5em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 7.5em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 7.5em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 9em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 10.5em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 9em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 10.5em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 13.5em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 13.5em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 15em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 16.5em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 15em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 16.5em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 19.5em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 19.5em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 21em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 22.5em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 21em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 22.5em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 24em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 25.5em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 24em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 25.5em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 27em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 28.5em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 27em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 28.5em;
}
.ql-editor .ql-video {
display: block;
max-width: 100%;
}
.ql-editor .ql-video.ql-align-center {
margin: 0 auto;
}
.ql-editor .ql-video.ql-align-right {
margin: 0 0 0 auto;
}
.ql-editor .ql-bg-black {
background-color: #000;
}
.ql-editor .ql-bg-red {
background-color: #e60000;
}
.ql-editor .ql-bg-orange {
background-color: #f90;
}
.ql-editor .ql-bg-yellow {
background-color: #ff0;
}
.ql-editor .ql-bg-green {
background-color: #008a00;
}
.ql-editor .ql-bg-blue {
background-color: #06c;
}
.ql-editor .ql-bg-purple {
background-color: #93f;
}
.ql-editor .ql-color-white {
color: #fff;
}
.ql-editor .ql-color-red {
color: #e60000;
}
.ql-editor .ql-color-orange {
color: #f90;
}
.ql-editor .ql-color-yellow {
color: #ff0;
}
.ql-editor .ql-color-green {
color: #008a00;
}
.ql-editor .ql-color-blue {
color: #06c;
}
.ql-editor .ql-color-purple {
color: #93f;
}
.ql-editor .ql-font-serif {
font-family: Georgia, Times New Roman, serif;
}
.ql-editor .ql-font-monospace {
font-family: Monaco, Courier New, monospace;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor.ql-blank::before {
color: rgba(0,0,0,0.6);
content: attr(data-placeholder);
left: 4px;
pointer-events: none;
position: absolute;
right: 4px;
}
.ql-snow.ql-toolbar:after,
.ql-snow .ql-toolbar:after {
clear: both;
content: '';
display: table;
}
.ql-snow.ql-toolbar button,
.ql-snow .ql-toolbar button {
background: none;
border: none;
cursor: pointer;
display: inline-block;
float: left;
height: 24px;
padding: 3px 5px;
width: 28px;
}
.ql-snow.ql-toolbar button svg,
.ql-snow .ql-toolbar button svg {
float: left;
height: 100%;
}
.ql-snow.ql-toolbar button:active:hover,
.ql-snow .ql-toolbar button:active:hover {
outline: none;
}
.ql-snow.ql-toolbar input.ql-image[type=file],
.ql-snow .ql-toolbar input.ql-image[type=file] {
display: none;
}
.ql-snow.ql-toolbar button:hover,
.ql-snow .ql-toolbar button:hover,
.ql-snow.ql-toolbar button:focus,
.ql-snow .ql-toolbar button:focus,
.ql-snow.ql-toolbar button.ql-active,
.ql-snow .ql-toolbar button.ql-active,
.ql-snow.ql-toolbar .ql-picker-label:hover,
.ql-snow .ql-toolbar .ql-picker-label:hover,
.ql-snow.ql-toolbar .ql-picker-label.ql-active,
.ql-snow .ql-toolbar .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker-item:hover,
.ql-snow .ql-toolbar .ql-picker-item:hover,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected {
color: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow .ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button:focus .ql-fill,
.ql-snow .ql-toolbar button:focus .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {
fill: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow .ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button:focus .ql-stroke,
.ql-snow .ql-toolbar button:focus .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-toolbar button.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow.ql-toolbar button:hover .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover .ql-stroke-miter,
.ql-snow.ql-toolbar button:focus .ql-stroke-miter,
.ql-snow .ql-toolbar button:focus .ql-stroke-miter,
.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {
stroke: #06c;
}
@media (pointer: coarse) {
.ql-snow.ql-toolbar button:hover:not(.ql-active),
.ql-snow .ql-toolbar button:hover:not(.ql-active) {
color: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
stroke: #444;
}
}
.ql-snow {
box-sizing: border-box;
}
.ql-snow * {
box-sizing: border-box;
}
.ql-snow .ql-hidden {
display: none;
}
.ql-snow .ql-out-bottom,
.ql-snow .ql-out-top {
visibility: hidden;
}
.ql-snow .ql-tooltip {
position: absolute;
transform: translateY(10px);
}
.ql-snow .ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-snow .ql-tooltip.ql-flip {
transform: translateY(-10px);
}
.ql-snow .ql-formats {
display: inline-block;
vertical-align: middle;
}
.ql-snow .ql-formats:after {
clear: both;
content: '';
display: table;
}
.ql-snow .ql-stroke {
fill: none;
stroke: #444;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.ql-snow .ql-stroke-miter {
fill: none;
stroke: #444;
stroke-miterlimit: 10;
stroke-width: 2;
}
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-empty {
fill: none;
}
.ql-snow .ql-even {
fill-rule: evenodd;
}
.ql-snow .ql-thin,
.ql-snow .ql-stroke.ql-thin {
stroke-width: 1;
}
.ql-snow .ql-transparent {
opacity: 0.4;
}
.ql-snow .ql-direction svg:last-child {
display: none;
}
.ql-snow .ql-direction.ql-active svg:last-child {
display: inline;
}
.ql-snow .ql-direction.ql-active svg:first-child {
display: none;
}
.ql-snow .ql-editor h1 {
font-size: 2em;
}
.ql-snow .ql-editor h2 {
font-size: 1.5em;
}
.ql-snow .ql-editor h3 {
font-size: 1.17em;
}
.ql-snow .ql-editor h4 {
font-size: 1em;
}
.ql-snow .ql-editor h5 {
font-size: 0.83em;
}
.ql-snow .ql-editor h6 {
font-size: 0.67em;
}
.ql-snow .ql-editor a {
text-decoration: underline;
}
.ql-snow .ql-editor blockquote {
border-left: 4px solid #ccc;
margin-bottom: 5px;
margin-top: 5px;
padding-left: 16px;
}
.ql-snow .ql-editor code,
.ql-snow .ql-editor pre {
background-color: #f0f0f0;
border-radius: 3px;
}
.ql-snow .ql-editor pre {
white-space: pre-wrap;
margin-bottom: 5px;
margin-top: 5px;
padding: 5px 10px;
}
.ql-snow .ql-editor code {
font-size: 85%;
padding: 2px 4px;
}
.ql-snow .ql-editor pre.ql-syntax {
background-color: #23241f;
color: #f8f8f2;
overflow: visible;
}
.ql-snow .ql-editor img {
max-width: 100%;
}
.ql-snow .ql-picker {
color: #444;
display: inline-block;
float: left;
font-size: 14px;
font-weight: 500;
height: 24px;
position: relative;
vertical-align: middle;
}
.ql-snow .ql-picker-label {
cursor: pointer;
display: inline-block;
height: 100%;
padding-left: 8px;
padding-right: 2px;
position: relative;
width: 100%;
}
.ql-snow .ql-picker-label::before {
display: inline-block;
line-height: 22px;
}
.ql-snow .ql-picker-options {
background-color: #fff;
display: none;
min-width: 100%;
padding: 4px 8px;
position: absolute;
white-space: nowrap;
}
.ql-snow .ql-picker-options .ql-picker-item {
cursor: pointer;
display: block;
padding-bottom: 5px;
padding-top: 5px;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
color: #ccc;
z-index: 2;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
fill: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
stroke: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
display: block;
margin-top: -1px;
top: 100%;
z-index: 1;
}
.ql-snow .ql-color-picker,
.ql-snow .ql-icon-picker {
width: 28px;
}
.ql-snow .ql-color-picker .ql-picker-label,
.ql-snow .ql-icon-picker .ql-picker-label {
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-label svg,
.ql-snow .ql-icon-picker .ql-picker-label svg {
right: 4px;
}
.ql-snow .ql-icon-picker .ql-picker-options {
padding: 4px 0px;
}
.ql-snow .ql-icon-picker .ql-picker-item {
height: 24px;
width: 24px;
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-options {
padding: 3px 5px;
width: 152px;
}
.ql-snow .ql-color-picker .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0px;
width: 16px;
}
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
position: absolute;
margin-top: -9px;
right: 0;
top: 50%;
width: 18px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {
content: attr(data-label);
}
.ql-snow .ql-picker.ql-header {
width: 98px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: 'Heading 1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: 'Heading 2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: 'Heading 3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: 'Heading 4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: 'Heading 5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: 'Heading 6';
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
font-size: 2em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
font-size: 1.5em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
font-size: 1.17em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
font-size: 1em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
font-size: 0.83em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
font-size: 0.67em;
}
.ql-snow .ql-picker.ql-font {
width: 108px;
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: 'Sans Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
content: 'Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
content: 'Monospace';
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
font-family: Georgia, Times New Roman, serif;
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
font-family: Monaco, Courier New, monospace;
}
.ql-snow .ql-picker.ql-size {
width: 98px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
content: 'Small';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
content: 'Large';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
content: 'Huge';
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
font-size: 32px;
}
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
background-color: #fff;
}
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
background-color: #000;
}
.ql-toolbar.ql-snow {
border: 1px solid #ccc;
box-sizing: border-box;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
padding: 8px;
}
.ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
}
.ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
}
.ql-toolbar.ql-snow .ql-picker-options {
border: 1px solid transparent;
box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
border-color: #000;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow {
border-top: 0px;
}
.ql-snow .ql-tooltip {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0px 0px 5px #ddd;
color: #444;
padding: 5px 12px;
white-space: nowrap;
}
.ql-snow .ql-tooltip::before {
content: "Visit URL:";
line-height: 26px;
margin-right: 8px;
}
.ql-snow .ql-tooltip input[type=text] {
display: none;
border: 1px solid #ccc;
font-size: 13px;
height: 26px;
margin: 0px;
padding: 3px 5px;
width: 170px;
}
.ql-snow .ql-tooltip a.ql-preview {
display: inline-block;
max-width: 200px;
overflow-x: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.ql-snow .ql-tooltip a.ql-action::after {
border-right: 1px solid #ccc;
content: 'Edit';
margin-left: 16px;
padding-right: 8px;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: 'Remove';
margin-left: 8px;
}
.ql-snow .ql-tooltip a {
line-height: 26px;
}
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
display: none;
}
.ql-snow .ql-tooltip.ql-editing input[type=text] {
display: inline-block;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: 'Save';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "Enter link:";
}
.ql-snow .ql-tooltip[data-mode=formula]::before {
content: "Enter formula:";
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "Enter video:";
}
.ql-snow a {
color: #06c;
}
+63 -55
View File
@@ -26,6 +26,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Messages.Mails; using Wino.Core.Messages.Mails;
using Wino.Core.Messages.Shell; using Wino.Core.Messages.Shell;
using Wino.Extensions; using Wino.Extensions;
@@ -167,42 +168,42 @@ namespace Wino.Views
private async void BoldButtonClicked(object sender, RoutedEventArgs e) private async void BoldButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('boldButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('bold')");
} }
private async void ItalicButtonClicked(object sender, RoutedEventArgs e) private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('italicButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('italic')");
} }
private async void UnderlineButtonClicked(object sender, RoutedEventArgs e) private async void UnderlineButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('underlineButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('underline')");
} }
private async void StrokeButtonClicked(object sender, RoutedEventArgs e) private async void StrokeButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('strikeButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('strikethrough')");
} }
private async void BulletListButtonClicked(object sender, RoutedEventArgs e) private async void BulletListButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('bulletListButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('insertunorderedlist')");
} }
private async void OrderedListButtonClicked(object sender, RoutedEventArgs e) private async void OrderedListButtonClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('orderedListButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('insertorderedlist')");
} }
private async void IncreaseIndentClicked(object sender, RoutedEventArgs e) private async void IncreaseIndentClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('increaseIndentButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('indent')");
} }
private async void DecreaseIndentClicked(object sender, RoutedEventArgs e) private async void DecreaseIndentClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('decreaseIndentButton').click();"); await InvokeScriptSafeAsync("editor.execCommand('outdent')");
} }
private async void DirectionButtonClicked(object sender, RoutedEventArgs e) private async void DirectionButtonClicked(object sender, RoutedEventArgs e)
@@ -218,16 +219,16 @@ namespace Wino.Views
switch (alignment) switch (alignment)
{ {
case "left": case "left":
await InvokeScriptSafeAsync("document.getElementById('ql-align-left').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyleft')");
break; break;
case "center": case "center":
await InvokeScriptSafeAsync("document.getElementById('ql-align-center').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifycenter')");
break; break;
case "right": case "right":
await InvokeScriptSafeAsync("document.getElementById('ql-align-right').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyright')");
break; break;
case "justify": case "justify":
await InvokeScriptSafeAsync("document.getElementById('ql-align-justify').click();"); await InvokeScriptSafeAsync("editor.execCommand('justifyfull')");
break; break;
} }
} }
@@ -254,19 +255,22 @@ namespace Wino.Views
{ {
return await Chromium.ExecuteScriptAsync(function); return await Chromium.ExecuteScriptAsync(function);
} }
catch (Exception) { } catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return string.Empty; return string.Empty;
} }
private async void AddImageClicked(object sender, RoutedEventArgs e) private async void AddImageClicked(object sender, RoutedEventArgs e)
{ {
await InvokeScriptSafeAsync("document.getElementById('addImageButton').click();"); await InvokeScriptSafeAsync("imageInput.click();");
} }
private async Task FocusEditorAsync() private async Task FocusEditorAsync()
{ {
await InvokeScriptSafeAsync("quill.focus();"); await InvokeScriptSafeAsync("editor.selection.focus();");
Chromium.Focus(FocusState.Keyboard); Chromium.Focus(FocusState.Keyboard);
Chromium.Focus(FocusState.Programmatic); Chromium.Focus(FocusState.Programmatic);
@@ -292,8 +296,6 @@ namespace Wino.Views
private async void LinkButtonClicked(object sender, RoutedEventArgs e) private async void LinkButtonClicked(object sender, RoutedEventArgs e)
{ {
// Get selected text from Quill.
HyperlinkTextBox.Text = await TryGetSelectedTextAsync(); HyperlinkTextBox.Text = await TryGetSelectedTextAsync();
} }
@@ -346,7 +348,7 @@ namespace Wino.Views
if (Chromium.CoreWebView2 != null) if (Chromium.CoreWebView2 != null)
{ {
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded; Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageRecieved; Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
} }
Chromium.Close(); Chromium.Close();
@@ -379,9 +381,9 @@ namespace Wino.Views
ViewModel.GetHTMLBodyFunction = new Func<Task<string>>(async () => ViewModel.GetHTMLBodyFunction = new Func<Task<string>>(async () =>
{ {
var quillContent = await InvokeScriptSafeAsync("GetHTMLContent();"); var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
return JsonConvert.DeserializeObject<string>(quillContent); return JsonConvert.DeserializeObject<string>(editorContent);
}); });
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>(); var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
@@ -391,7 +393,7 @@ namespace Wino.Views
private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args) private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args)
{ {
var editorBundlePath = (await ViewModel.NativeAppService.GetQuillEditorBundlePathAsync()).Replace("editor.html", string.Empty); var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow); Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
Chromium.Source = new Uri("https://app.editor/editor.html"); Chromium.Source = new Uri("https://app.editor/editor.html");
@@ -399,52 +401,58 @@ namespace Wino.Views
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded; Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
Chromium.CoreWebView2.DOMContentLoaded += DOMLoaded; Chromium.CoreWebView2.DOMContentLoaded += DOMLoaded;
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageRecieved; Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
Chromium.CoreWebView2.WebMessageReceived += ScriptMessageRecieved; Chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived;
} }
private void ScriptMessageRecieved(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args) private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{ {
var change = JsonConvert.DeserializeObject<string>(args.WebMessageAsJson); var change = JsonConvert.DeserializeObject<WebViewMessage>(args.WebMessageAsJson);
bool isEnabled = change.EndsWith("ql-active"); if (change.type == "bold")
if (change.StartsWith("ql-bold"))
BoldButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-italic"))
ItalicButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-underline"))
UnderlineButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-strike"))
StrokeButton.IsChecked = isEnabled;
else if (change.StartsWith("orderedListButton"))
OrderedListButton.IsChecked = isEnabled;
else if (change.StartsWith("bulletListButton"))
BulletListButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-direction"))
DirectionButton.IsChecked = isEnabled;
else if (change.StartsWith("ql-align-left"))
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; BoldButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 0;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-center")) else if (change.type == "italic")
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; ItalicButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 1;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-right")) else if (change.type == "underline")
{ {
AlignmentListView.SelectionChanged -= AlignmentChanged; UnderlineButton.IsChecked = change.value == "true";
AlignmentListView.SelectedIndex = 2;
AlignmentListView.SelectionChanged += AlignmentChanged;
} }
else if (change.StartsWith("ql-align-justify")) else if (change.type == "strikethrough")
{ {
StrokeButton.IsChecked = change.value == "true";
}
else if (change.type == "ol")
{
OrderedListButton.IsChecked = change.value == "true";
}
else if (change.type == "ul")
{
BulletListButton.IsChecked = change.value == "true";
}
else if (change.type == "indent")
{
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
}
else if (change.type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
}
else if (change.type == "alignment")
{
var parsedValue = change.value switch
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,
"jodit-icon_right" => 2,
"jodit-icon_justify" => 3,
_ => 0
};
AlignmentListView.SelectionChanged -= AlignmentChanged; AlignmentListView.SelectionChanged -= AlignmentChanged;
AlignmentListView.SelectedIndex = 3; AlignmentListView.SelectedIndex = parsedValue;
AlignmentListView.SelectionChanged += AlignmentChanged; AlignmentListView.SelectionChanged += AlignmentChanged;
} }
} }
+1 -1
View File
@@ -184,7 +184,7 @@ namespace Wino.Views
{ {
if (Chromium.CoreWebView2 == null) return; if (Chromium.CoreWebView2 == null) return;
var editorBundlePath = (await ViewModel.NativeAppService.GetQuillEditorBundlePathAsync()).Replace("editor.html", string.Empty); var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.reader", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow); Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.reader", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
+2 -2
View File
@@ -758,9 +758,9 @@
<Content Include="JS\Quill\editor.html" /> <Content Include="JS\Quill\editor.html" />
<Content Include="JS\Quill\editor.js" /> <Content Include="JS\Quill\editor.js" />
<Content Include="JS\Quill\global.css" /> <Content Include="JS\Quill\global.css" />
<Content Include="JS\Quill\libs\quill.js" />
<Content Include="Assets\WinoIcons.ttf" /> <Content Include="Assets\WinoIcons.ttf" />
<Content Include="JS\Quill\libs\quill.snow.css" /> <Content Include="JS\Quill\libs\jodit.min.css" />
<Content Include="JS\Quill\libs\jodit.min.js" />
<Content Include="JS\Quill\reader.html" /> <Content Include="JS\Quill\reader.html" />
<Content Include="Assets\ReleaseNotes\172.md" /> <Content Include="Assets\ReleaseNotes\172.md" />
<None Include="Package.StoreAssociation.xml" /> <None Include="Package.StoreAssociation.xml" />