Improve design by factoring out HTML editor related subs into a lib
This commit is contained in:
parent
61e80f5d9f
commit
a353a11653
209
html-editor-lib.pl
Normal file
209
html-editor-lib.pl
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# html-editor-lib.pl
|
||||||
|
# Quill HTML editor related subs
|
||||||
|
|
||||||
|
sub html_editor_load_bundle
|
||||||
|
{
|
||||||
|
my $wp = &get_webprefix();
|
||||||
|
my $ts = &get_webmin_version();
|
||||||
|
$ts =~ s/[.-]+//g;
|
||||||
|
my $html_editor_load_scripts =
|
||||||
|
<<EOF;
|
||||||
|
<link href="$wp/unauthenticated/css/quill.min.css?$ts" rel="stylesheet">
|
||||||
|
<script type="text/javascript" src="$wp/unauthenticated/js/quill.min.js?$ts"></script>
|
||||||
|
EOF
|
||||||
|
return $html_editor_load_scripts;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub html_editor_template
|
||||||
|
{
|
||||||
|
my ($opts) = @_;
|
||||||
|
$opts ||= {};
|
||||||
|
$html_editor_template =
|
||||||
|
<<EOF;
|
||||||
|
$opts->{'before'}->{'container'}
|
||||||
|
<div class="ql-compose-container $opts->{'class'}->{'container'}">
|
||||||
|
$opts->{'before'}->{'editor'}
|
||||||
|
<div data-composer="html" class="ql-compose ql-container $opts->{'class'}->{'editor'}"></div>
|
||||||
|
$opts->{'after'}->{'editor'}
|
||||||
|
</div>
|
||||||
|
$opts->{'after'}->{'container'}
|
||||||
|
EOF
|
||||||
|
return $html_editor_template;
|
||||||
|
}
|
||||||
|
sub html_editor_styles
|
||||||
|
{
|
||||||
|
my ($type) = @_;
|
||||||
|
|
||||||
|
# HTML editor toolbar styles
|
||||||
|
if ($type eq 'toolbar') {
|
||||||
|
return
|
||||||
|
<<EOF;
|
||||||
|
<style>
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||||
|
content: '$text{'editor_fontfamily_default'}';
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="0.75em"]::before,
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.75em"]::before {
|
||||||
|
content: '$text{'editor_font_small'}';
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.75em"]::before {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||||
|
content: '$text{'editor_font_normal'}';
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="1.15em"]::before,
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.15em"]::before {
|
||||||
|
content: '$text{'editor_font_medium'}';
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.15em"]::before {
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="1.3em"]::before,
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.3em"]::before {
|
||||||
|
content: '$text{'editor_font_large'}';
|
||||||
|
}
|
||||||
|
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.3em"]::before {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub html_editor_parts
|
||||||
|
{
|
||||||
|
my ($part, $type) = @_;
|
||||||
|
|
||||||
|
# Editor toolbars
|
||||||
|
if ($part eq 'toolbar') {
|
||||||
|
# Toolbar for mail editor
|
||||||
|
if ($type eq 'mail') {
|
||||||
|
return
|
||||||
|
<<EOF;
|
||||||
|
[
|
||||||
|
[{'font': [false, 'monospace']},
|
||||||
|
{'size': ['0.75em', false, "1.15em", '1.3em']}],
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{'color': []}, {'background': []}],
|
||||||
|
[{'align': []}],
|
||||||
|
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||||
|
[{'indent': '-1'}, {'indent': '+1'}],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
['link', 'image'],
|
||||||
|
[{'direction': 'rtl'}],
|
||||||
|
['clean']
|
||||||
|
]
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
sub html_editor_init_script
|
||||||
|
{
|
||||||
|
my ($type, $opts) = @_;
|
||||||
|
$opts ||= {};
|
||||||
|
my $html_editor_init_script =
|
||||||
|
<<EOF;
|
||||||
|
<script type="text/javascript">
|
||||||
|
const mail_init_editor = function() {
|
||||||
|
const targ = document.querySelector('[name="body"]'),
|
||||||
|
qs = Quill.import('attributors/style/size'),
|
||||||
|
qf = Quill.import('attributors/style/font'),
|
||||||
|
escapeHTML_ = function(htmlStr) {
|
||||||
|
return htmlStr.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
},
|
||||||
|
isMac = navigator.userAgent.toLowerCase().includes('mac');
|
||||||
|
|
||||||
|
qs.whitelist = ["0.75em", "1.15em", "1.3em"];
|
||||||
|
Quill.register(qs, true);
|
||||||
|
qf.whitelist = ["monospace"],
|
||||||
|
Quill.register(qf, true);
|
||||||
|
|
||||||
|
// Whitelist attrs
|
||||||
|
const pc = Quill.import('parchment'),
|
||||||
|
pc_attrs_whitelist =
|
||||||
|
[
|
||||||
|
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||||
|
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||||
|
'border', 'border-right', 'border-left',
|
||||||
|
'font-size', 'font-family', 'href', 'target',
|
||||||
|
]
|
||||||
|
pc_attrs_whitelist.forEach(function(attr) {
|
||||||
|
Quill.register(new pc.Attributor.Style(attr, attr, {}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = new Quill('.ql-container', {
|
||||||
|
modules: {
|
||||||
|
formula: false,
|
||||||
|
syntax: false,
|
||||||
|
imageDrop: true,
|
||||||
|
imageResize: {
|
||||||
|
modules: [
|
||||||
|
'DisplaySize',
|
||||||
|
'Resize',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
toolbar: @{[&html_editor_parts('toolbar', $type)]},
|
||||||
|
},
|
||||||
|
bounds: '.ql-compose-container',
|
||||||
|
theme: 'snow'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Google Mail editor like keybind for quoting
|
||||||
|
editor.keyboard.addBinding({
|
||||||
|
key: '9',
|
||||||
|
shiftKey: true,
|
||||||
|
ctrlKey: !isMac,
|
||||||
|
metaKey: isMac,
|
||||||
|
format: ['blockquote'],
|
||||||
|
}, function(range, context) {
|
||||||
|
this.quill.format('blockquote', false);
|
||||||
|
});
|
||||||
|
editor.keyboard.addBinding({
|
||||||
|
key: '9',
|
||||||
|
shiftKey: true,
|
||||||
|
ctrlKey: !isMac,
|
||||||
|
metaKey: isMac,
|
||||||
|
}, function(range, context) {
|
||||||
|
this.quill.format('blockquote', true);
|
||||||
|
});
|
||||||
|
editor.on('text-change', function() {
|
||||||
|
targ.value = escapeHTML_(editor.root.innerHTML + "<br><br>");
|
||||||
|
let quoteHTML = String(),
|
||||||
|
err = false;
|
||||||
|
try {
|
||||||
|
quoteHTML =
|
||||||
|
document.querySelector('#quote-mail-iframe')
|
||||||
|
.contentWindow.document
|
||||||
|
.querySelector('.iframe_quote[contenteditable]#webmin-iframe-quote').innerHTML;
|
||||||
|
} catch(e) {
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
targ.value = targ.value + escapeHTML_(quoteHTML);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editor.pasteHTML(targ.value);
|
||||||
|
|
||||||
|
// Prevent loosing focus for toolbar selects (color picker, font select and etc)
|
||||||
|
editor.getModule("toolbar").container.addEventListener("mousedown", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@{[$opts->{'load'} ? 'mail_init_editor()' : '']}
|
||||||
|
</script>
|
||||||
|
EOF
|
||||||
|
return $html_editor_init_script;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -2,6 +2,7 @@
|
|||||||
# Display a form for replying to or composing an email
|
# Display a form for replying to or composing an email
|
||||||
|
|
||||||
require './mailboxes-lib.pl';
|
require './mailboxes-lib.pl';
|
||||||
|
require '../html-editor-lib.pl';
|
||||||
&ReadParse();
|
&ReadParse();
|
||||||
&can_user($in{'user'}) || &error($text{'mail_ecannot'});
|
&can_user($in{'user'}) || &error($text{'mail_ecannot'});
|
||||||
@uinfo = &get_mail_user($in{'user'});
|
@uinfo = &get_mail_user($in{'user'});
|
||||||
@ -448,174 +449,15 @@ my $html_editor_styles;
|
|||||||
my $html_editor_scripts;
|
my $html_editor_scripts;
|
||||||
my $html_editor_load_scripts;
|
my $html_editor_load_scripts;
|
||||||
if ($html_edit) {
|
if ($html_edit) {
|
||||||
$html_editor_template = <<EOF;
|
$html_editor_template = &html_editor_template({after => {editor => $html_editor_quote}});
|
||||||
<div class="ql-compose-container">
|
$html_editor_styles = &html_editor_styles('toolbar');
|
||||||
<div data-composer="html" class="ql-compose ql-container ql-container-toolbar-bottom"></div>
|
|
||||||
$html_editor_quote
|
|
||||||
</div>
|
|
||||||
EOF
|
|
||||||
$html_editor_styles = <<EOF;
|
|
||||||
<style>
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
|
||||||
content: '$text{'editor_fontfamily_default'}';
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="0.75em"]::before,
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.75em"]::before {
|
|
||||||
content: '$text{'editor_font_small'}';
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.75em"]::before {
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
|
||||||
content: '$text{'editor_font_normal'}';
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="1.15em"]::before,
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.15em"]::before {
|
|
||||||
content: '$text{'editor_font_medium'}';
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.15em"]::before {
|
|
||||||
font-size: 1.15em;
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="1.3em"]::before,
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.3em"]::before {
|
|
||||||
content: '$text{'editor_font_large'}';
|
|
||||||
}
|
|
||||||
.ql-compose-container .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="1.3em"]::before {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
EOF
|
|
||||||
my %tinfo = &get_theme_info($current_theme);
|
my %tinfo = &get_theme_info($current_theme);
|
||||||
if (!$tinfo{'spa'}) {
|
if (!$tinfo{'spa'}) {
|
||||||
# Load HTML editor files
|
# Load HTML editor files
|
||||||
my $ts = &get_webmin_version();
|
$html_editor_load_scripts = &html_editor_load_bundle();
|
||||||
$ts =~ s/[.-]+//g;
|
|
||||||
$html_editor_load_scripts = <<EOF;
|
|
||||||
<link href="/unauthenticated/css/quill.min.css?$ts" rel="stylesheet">
|
|
||||||
<script type="text/javascript" src="/unauthenticated/js/quill.min.js?$ts"></script>
|
|
||||||
EOF
|
|
||||||
}
|
}
|
||||||
# HTML editor init
|
# HTML editor init
|
||||||
$html_editor_scripts = <<EOF;
|
$html_editor_scripts = &html_editor_init_script('mail', {load => !$tinfo{'spa'}});
|
||||||
<script type="text/javascript">
|
|
||||||
const mail_init_editor = function() {
|
|
||||||
const targ = document.querySelector('[name="body"]'),
|
|
||||||
qs = Quill.import('attributors/style/size'),
|
|
||||||
qf = Quill.import('attributors/style/font'),
|
|
||||||
escapeHTML_ = function(htmlStr) {
|
|
||||||
return htmlStr.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
},
|
|
||||||
isMac = navigator.userAgent.toLowerCase().includes('mac');
|
|
||||||
|
|
||||||
qs.whitelist = ["0.75em", "1.15em", "1.3em"];
|
|
||||||
Quill.register(qs, true);
|
|
||||||
qf.whitelist = ["monospace"],
|
|
||||||
Quill.register(qf, true);
|
|
||||||
|
|
||||||
// Whitelist attrs
|
|
||||||
const pc = Quill.import('parchment'),
|
|
||||||
pc_attrs_whitelist =
|
|
||||||
[
|
|
||||||
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
|
||||||
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
|
||||||
'border', 'border-right', 'border-left',
|
|
||||||
'font-size', 'font-family', 'href', 'target',
|
|
||||||
]
|
|
||||||
pc_attrs_whitelist.forEach(function(attr) {
|
|
||||||
Quill.register(new pc.Attributor.Style(attr, attr, {}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const editor = new Quill('.ql-container', {
|
|
||||||
modules: {
|
|
||||||
formula: false,
|
|
||||||
syntax: false,
|
|
||||||
imageDrop: true,
|
|
||||||
imageResize: {
|
|
||||||
modules: [
|
|
||||||
'DisplaySize',
|
|
||||||
'Resize',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
toolbar:
|
|
||||||
[
|
|
||||||
[{ 'font': [false, 'monospace'] },
|
|
||||||
{ 'size': ['0.75em', false, "1.15em", '1.3em'] }],
|
|
||||||
['bold', 'italic', 'underline', 'strike'],
|
|
||||||
[{ 'color': [] }, { 'background': [] }],
|
|
||||||
[{ 'align': [] }],
|
|
||||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
|
||||||
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
|
||||||
|
|
||||||
['blockquote', 'code-block'],
|
|
||||||
['link', 'image'],
|
|
||||||
[{ 'direction': 'rtl' }],
|
|
||||||
|
|
||||||
['clean']
|
|
||||||
],
|
|
||||||
},
|
|
||||||
bounds: '.ql-compose-container',
|
|
||||||
theme: 'snow'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Google Mail editor like keybind for quoting
|
|
||||||
editor.keyboard.addBinding({
|
|
||||||
key: '9',
|
|
||||||
shiftKey: true,
|
|
||||||
ctrlKey: !isMac,
|
|
||||||
metaKey: isMac,
|
|
||||||
format: ['blockquote'],
|
|
||||||
}, function(range, context) {
|
|
||||||
this.quill.format('blockquote', false);
|
|
||||||
});
|
|
||||||
editor.keyboard.addBinding({
|
|
||||||
key: '9',
|
|
||||||
shiftKey: true,
|
|
||||||
ctrlKey: !isMac,
|
|
||||||
metaKey: isMac,
|
|
||||||
}, function(range, context) {
|
|
||||||
this.quill.format('blockquote', true);
|
|
||||||
});
|
|
||||||
editor.on('text-change', function() {
|
|
||||||
targ.value = escapeHTML_(editor.root.innerHTML + "<br><br>");
|
|
||||||
let quoteHTML = String(),
|
|
||||||
err = false;
|
|
||||||
try {
|
|
||||||
quoteHTML =
|
|
||||||
document.querySelector('#quote-mail-iframe')
|
|
||||||
.contentWindow.document
|
|
||||||
.querySelector('.iframe_quote[contenteditable]#webmin-iframe-quote').innerHTML;
|
|
||||||
} catch(e) {
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
if (!err) {
|
|
||||||
targ.value = targ.value + escapeHTML_(quoteHTML);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editor.pasteHTML(targ.value);
|
|
||||||
|
|
||||||
// Prevent loosing focus for toolbar selects (color picker, font select and etc)
|
|
||||||
editor.getModule("toolbar").container.addEventListener("mousedown", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
EOF
|
|
||||||
if (!$tinfo{'spa'}) {
|
|
||||||
$html_editor_scripts .= <<EOF;
|
|
||||||
<script type="text/javascript">
|
|
||||||
mail_init_editor();
|
|
||||||
</script>
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
$sig =~ s/\n/<br>\n/g,
|
$sig =~ s/\n/<br>\n/g,
|
||||||
$sig =~ s/^\s+//g
|
$sig =~ s/^\s+//g
|
||||||
if ($sig);
|
if ($sig);
|
||||||
|
@ -59,7 +59,7 @@ $vers || usage();
|
|||||||
"uptracker.cgi", "create-module.pl", "webmin_search.cgi",
|
"uptracker.cgi", "create-module.pl", "webmin_search.cgi",
|
||||||
"webmin-search-lib.pl", "WebminCore.pm",
|
"webmin-search-lib.pl", "WebminCore.pm",
|
||||||
"record-login.pl", "record-logout.pl", "record-failed.pl",
|
"record-login.pl", "record-logout.pl", "record-failed.pl",
|
||||||
"robots.txt", "unauthenticated", "bin",
|
"robots.txt", "unauthenticated", "bin", "html-editor-lib.pl",
|
||||||
);
|
);
|
||||||
if ($min) {
|
if ($min) {
|
||||||
# Only those required by others
|
# Only those required by others
|
||||||
|
Loading…
x
Reference in New Issue
Block a user