diff --git a/README.md b/README.md
index 2d7860569..79c186978 100644
--- a/README.md
+++ b/README.md
@@ -88,23 +88,23 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
|js/* |1.14.0 |native |
|----------- |--------: |--------: |
-|admin.js |2.130.942 | 986.303 |
-|app.js |4.184.455 |2.603.659 |
+|admin.js |2.130.942 | 990.560 |
+|app.js |4.184.455 |2.607.924 |
|boot.js | 671.522 | 5.834 |
|libs.js | 647.614 | 312.343 |
|polyfills.js | 325.834 | 0 |
-|TOTAL |7.960.367 |3.908.139 |
+|TOTAL |7.960.367 |3.916.661 |
|js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|--------------- |--------: |--------: |--------: |--------: |--------: |
-|admin.min.js | 252.147 | 134.678 | 73.657 | 39.834 | 34.181 |
-|app.min.js | 511.202 | 355.203 |140.462 | 94.064 | 75.648 |
+|admin.min.js | 252.147 | 135.441 | 73.657 | 40.158 | 34.434 |
+|app.min.js | 511.202 | 355.966 |140.462 | 94.405 | 75.847 |
|boot.min.js | 66.007 | 3.166 | 22.567 | 1.571 | 1.345 |
|libs.min.js | 572.545 | 295.754 |176.720 | 91.521 | 80.871 |
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
-|TOTAL |1.434.353 | 788.801 |424.718 |226.990 |192.045 |
+|TOTAL |1.434.353 | 790.327 |424.718 |227.655 |192.497 |
-657.410 bytes (202.330 gzip) is not much, but it feels faster.
+644.026 bytes (197.063 gzip) is not much, but it feels faster.
### CSS changes
@@ -129,8 +129,8 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|-------------- |-------: |-------: |------: |------: |------: |
-|app.css | 340.334 | 252.797 | 46,959 | 36.394 | 30.615 |
-|app.min.css | 274.791 | 206.474 | 39.618 | 32.238 | 27.339 |
+|app.css | 340.334 | 253.711 | 46,959 | 36.587 | 30.802 |
+|app.min.css | 274.791 | 207.227 | 39.618 | 31.993 | 27.160 |
|boot.css | | 2.538 | | 837 | 668 |
|boot.min.css | | 2.055 | | 732 | 560 |
@@ -142,7 +142,7 @@ Still TODO:
* upload image inline
* support for tables (really needed?!?)
-* toggle between HTML and plain
+* alternative for vendors/ckeditor-plugins/signature/plugin.js
| | normal | min | gzip | min gzip |
|-------- |-------: |-------: |------: |--------: |
diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js
index 084c98ffc..bb6c1e1d1 100644
--- a/dev/Common/HtmlEditor.js
+++ b/dev/Common/HtmlEditor.js
@@ -267,7 +267,21 @@ class HtmlEditor {
init() {
if (this.element && !this.editor) {
- const initFunc = () => {
+ const onReady = () => {
+ if (this.editor.removeMenuItem) {
+ this.editor.removeMenuItem('cut');
+ this.editor.removeMenuItem('copy');
+ this.editor.removeMenuItem('paste');
+ }
+
+ this.__resizable = true;
+ this.__inited = true;
+
+ this.resize();
+
+ this.onReady && this.onReady();
+ },
+ initFunc = () => {
if (window.CKEDITOR) {
const config = CKEditorDefaultConfig,
language = rl.settings.get('Language'),
@@ -298,15 +312,6 @@ class HtmlEditor {
this.editor.on('key', event => !(event && event.data && EventKeyCode.Tab === event.data.keyCode));
- this.editor.on('blur', () => this.blurTrigger());
-
- this.editor.on('mode', () => {
- this.blurTrigger();
- this.onModeChange && this.onModeChange('plain' !== this.editor.mode);
- });
-
- this.editor.on('focus', () => this.focusTrigger());
-
if (window.FileReader) {
this.editor.on('drop', (event) => {
if (0 < event.data.dataTransfer.getFilesCount()) {
@@ -330,32 +335,13 @@ class HtmlEditor {
});
}
- this.editor.on('instanceReady', () => {
- if (this.editor.removeMenuItem) {
- this.editor.removeMenuItem('cut');
- this.editor.removeMenuItem('copy');
- this.editor.removeMenuItem('paste');
- }
-
- this.__resizable = true;
- this.__inited = true;
-
- this.resize();
-
- this.onReady && this.onReady();
- });
+ this.editor.on('instanceReady', onReady);
}
else if (window.Squire) {
this.editor = new SquireUI(this.element, this.editor);
- this.editor.on('blur', () => this.blurTrigger());
- this.editor.on('focus', () => this.focusTrigger());
/*
// TODO
this.editor.on('key', event => !(event && event.data && EventKeyCode.Tab === event.data.keyCode));
- this.editor.on('mode', () => {
- this.blurTrigger();
- this.onModeChange && this.onModeChange('plain' !== this.editor.mode);
- });
if (window.FileReader) {
this.editor.on('dragover', (event) => {
event.dataTransfer = clipboardData
@@ -383,12 +369,16 @@ class HtmlEditor {
});
}
*/
- this.__resizable = true;
- this.__inited = true;
+ setTimeout(onReady,1);
+ }
- this.resize();
-
- this.onReady && setTimeout(() => this.onReady(), 1);
+ if (this.editor) {
+ this.editor.on('blur', () => this.blurTrigger());
+ this.editor.on('focus', () => this.focusTrigger());
+ this.editor.on('mode', () => {
+ this.blurTrigger();
+ this.onModeChange && this.onModeChange('plain' !== this.editor.mode);
+ });
}
};
diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js
index a64bbcd5a..5777f6d95 100644
--- a/dev/Common/Utils.js
+++ b/dev/Common/Utils.js
@@ -498,8 +498,10 @@ export function plainToHtml(plain) {
.replace(/\n/g, '
');
}
-window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation
-window['rainloop_Utils_plainToHtml'] = plainToHtml; // eslint-disable-line dot-notation
+rl.Utils = {
+ htmlToPlain: htmlToPlain,
+ plainToHtml: plainToHtml
+};
/**
* @param {Array} aSystem
diff --git a/dev/External/SquireUI.js b/dev/External/SquireUI.js
index 47ca9dfce..a5a90e1a2 100644
--- a/dev/External/SquireUI.js
+++ b/dev/External/SquireUI.js
@@ -1,10 +1,13 @@
/* eslint max-len: 0 */
+'use strict';
const doc = document,
allowedElements = 'A,B,BLOCKQUOTE,BR,DIV,FONT,H1,H2,H3,H4,H5,H6,HR,IMG,LABEL,LI,OL,P,SPAN,STRONG,TABLE,TD,TH,TR,U,UL',
allowedAttributes = 'abbr,align,background,bgcolor,border,cellpadding,cellspacing,class,color,colspan,dir,face,frame,height,href,hspace,id,lang,rowspan,rules,scope,size,src,style,target,type,usemap,valign,vspace,width'.split(','),
+i18n = (str, def) => window.rl && window.rl.i18n ? window.rl.i18n(str) : def,
+
SquireDefaultConfig = {
/*
blockTag: 'P',
@@ -33,11 +36,12 @@ SquireDefaultConfig = {
addLinks: true // allow_smart_html_links
*/
sanitizeToDOMFragment: (html, isPaste/*, squire*/) => {
- const frag = doc.createDocumentFragment();
- frag.innerHTML = html;
+ const frag = doc.createDocumentFragment(),
+ tpl = doc.createElement('div');
+ tpl.innerHTML = html;
if (isPaste) {
- frag.querySelectorAll(':not('+allowedElements+')').forEach(el => el.remove());
- frag.querySelectorAll(allowedElements).forEach(el => {
+ tpl.querySelectorAll(':not('+allowedElements+')').forEach(el => el.remove());
+ tpl.querySelectorAll(allowedElements).forEach(el => {
if (el.hasAttributes()) {
Array.from(el.attributes).forEach(attr => {
let name = attr.name.toLowerCase();
@@ -48,6 +52,7 @@ SquireDefaultConfig = {
}
});
}
+ frag.append(...tpl.childNodes);
return frag;
}
};
@@ -58,10 +63,11 @@ class SquireUI
const
ctrlKey = /Mac OS X/.test( navigator.userAgent ) ? 'meta + ' : 'Ctrl + ',
actions = {
- group1: {
- source: {
+ mode: {
+ plain: {
html: '〈〉',
- cmd: () => console.log('TODO: toggle HTML <> Text')
+ cmd: () => this.setMode('plain' == this.mode ? 'wysiwyg' : 'plain'),
+ hint: i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT', 'Plain')
}
},
font: {
@@ -100,7 +106,7 @@ class SquireUI
backgroundColor: {
input: 'color',
cmd: s => squire.setHighlightColour(s.value),
- hint: 'background'
+ hint: i18n('EDITOR/TEXT_BACKGROUND_COLOR', 'Background')
},
},
/*
@@ -185,7 +191,7 @@ class SquireUI
html: '🖼️',
cmd: () => {
if ('IMG' === this.getParentNodeName()) {
-// wysiwyg.removeLink();
+// squire.removeLink();
} else {
let src = prompt("Image","https://");
src != null && src.length && squire.insertImage(src);
@@ -217,19 +223,29 @@ class SquireUI
}
},
- content = doc.createElement('div'),
+ plain = doc.createElement('textarea'),
+ wysiwyg = doc.createElement('div'),
toolbar = doc.createElement('div'),
- squire = new Squire(content, SquireDefaultConfig);
+ squire = new Squire(wysiwyg, SquireDefaultConfig);
- content.className = 'squire-content cke_wysiwyg_div cke_editable';
+ plain.className = 'squire-plain cke_plain cke_editable';
+ wysiwyg.className = 'squire-wysiwyg cke_wysiwyg_div cke_editable';
+ this.mode = 'plain'; // 'wysiwyg'
+ this.__plain = {
+ getRawData: () => this.plain.value,
+ setRawData: plain => this.plain.value = plain
+ };
+ this.container = container;
this.squire = squire;
- this.content = content;
+ this.plain = plain;
+ this.wysiwyg = wysiwyg;
toolbar.className = 'squire-toolbar cke_top';
for (let group in actions) {
let toolgroup = doc.createElement('div');
toolgroup.className = 'squire-toolgroup cke_toolgroup';
+ toolgroup.id = 'squire-toolgroup-'+group;
for (let action in actions[group]) {
if ('source' == action && !rl.settings.app('allowHtmlEditorSourceButton')) {
continue;
@@ -279,7 +295,9 @@ class SquireUI
}
toolbar.addEventListener('click', e => {
let t = e.target;
- t.action_cmd && t.action_cmd(t);
+ if ('plain' != this.mode || 'plain' == t.dataset.action) {
+ t.action_cmd && t.action_cmd(t);
+ }
});
let changes = actions.changes
@@ -289,7 +307,7 @@ class SquireUI
changes.redo.input.disabled = !state.canRedo;
});
- container.append(toolbar, content);
+ container.append(toolbar, wysiwyg, plain);
/*
squire-raw.js:2161: this.fireEvent( 'dragover', {
@@ -305,9 +323,8 @@ squire-raw.js:4089: this.fireEvent( 'willPaste', event );
*/
// CKEditor gimmicks used by HtmlEditor
- this.mode = 'wysiwyg'; // 'plain'
this.plugins = {
- plain: false
+ plain: true
};
// .plugins.plain && this.editor.__plain
this.focusManager = {
@@ -338,13 +355,31 @@ squire-raw.js:4089: this.fireEvent( 'willPaste', event );
return validation.test(this.squire.getPath()) | this.squire.hasFormat(format);
}
- // CKeditor gimmicks used by HtmlEditor
setMode(mode) {
+ let cl = this.container.classList;
+ cl.remove('squire-mode-'+this.mode);
+ if ('plain' == mode) {
+ let html = this.squire.getHTML();
+ this.plain.value = rl.Utils.htmlToPlain(html, true).trim();
+ } else {
+ let plain = this.plain.value;
+ this.squire.setHTML(rl.Utils.plainToHtml(plain, true));
+ mode = 'wysiwyg';
+ }
this.mode = mode; // 'wysiwyg' or 'plain'
+ cl.add('squire-mode-'+mode);
+ this.onModeChange && this.onModeChange();
+ setTimeout(()=>this.focus(),1);
}
+ // CKeditor gimmicks used by HtmlEditor
on(type, fn) {
- this.squire.addEventListener(type, fn);
+ if ('mode' == type) {
+ this.onModeChange = fn;
+ } else {
+ this.squire.addEventListener(type, fn);
+ this.plain.addEventListener(type, fn);
+ }
}
execCommand(cmd, cfg) {
@@ -374,15 +409,18 @@ squire-raw.js:4089: this.fireEvent( 'willPaste', event );
}
focus() {
- this.squire.focus();
+ ('plain' == this.editor.mode ? this.plain : this.squire).focus();
}
resize(width, height) {
- this.content.style.height = Math.max(200, (height - this.content.offsetTop)) + 'px';
+ height = Math.max(200, (height - this.wysiwyg.offsetTop)) + 'px';
+ this.wysiwyg.style.height = height;
+ this.plain.style.height = height;
}
setReadOnly(bool) {
- this.content.contentEditable = !!bool;
+ this.plain.readOnly = !!bool;
+ this.wysiwyg.contentEditable = !!bool;
}
}
diff --git a/dev/Styles/SquireUI.less b/dev/Styles/SquireUI.less
index 79060fc4f..ffccbc1b6 100644
--- a/dev/Styles/SquireUI.less
+++ b/dev/Styles/SquireUI.less
@@ -33,7 +33,24 @@
width: 4em;
}
-.squire-content {
+.squire-wysiwyg, .squire-plain {
min-height: 200px;
padding: .5em;
}
+
+.squire-plain {
+ width: 100%;
+}
+
+.squire-plain {
+ display: none;
+}
+.squire-mode-plain .squire-wysiwyg {
+ display: none;
+}
+.squire-mode-plain .squire-plain {
+ display: block;
+}
+.squire-mode-plain .squire-toolgroup:not(#squire-toolgroup-mode) {
+ opacity: 0.5;
+}
diff --git a/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml b/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml
index f7c68a2fb..29bf3d429 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/_source.en.yml
@@ -418,8 +418,6 @@ en:
ERROR_UNKNOWN: "An unknown file upload error occurred"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEXT"
- TEXT_SWITCHER_RICH_FORMATTING: "Rich formatting"
- TEXT_SWITCHER_CONFIRM: "Text formatting and images will be lost. Are you sure you want to continue?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Personal"
LABEL_GENERAL_NAME: "General"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/de_DE.yml b/rainloop/v/0.0.0/app/localization/webmail/de_DE.yml
index a9fc19f7d..026899c96 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/de_DE.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/de_DE.yml
@@ -417,8 +417,6 @@ de_DE:
ERROR_UNKNOWN: "Ein unbekannter Fehler trat beim Hochladen auf"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEXT"
- TEXT_SWITCHER_RICH_FORMATTING: "Formatierter Text (Rich Text)"
- TEXT_SWITCHER_CONFIRM: "Alle Textformatierungen und Grafiken gehen verloren. Wollen Sie wirklich fortfahren?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Persönlich"
LABEL_GENERAL_NAME: "Allgemein"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/en_GB.yml b/rainloop/v/0.0.0/app/localization/webmail/en_GB.yml
index 703fba214..2370701b3 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/en_GB.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/en_GB.yml
@@ -417,8 +417,6 @@ en_GB:
ERROR_UNKNOWN: "An unknown file upload error occurred"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEXT"
- TEXT_SWITCHER_RICH_FORMATTING: "Rich formatting"
- TEXT_SWITCHER_CONFIRM: "Text formatting and images will be lost. Are you sure you want to continue?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Personal"
LABEL_GENERAL_NAME: "General"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/en_US.yml b/rainloop/v/0.0.0/app/localization/webmail/en_US.yml
index 16b4f2692..06083a6bd 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/en_US.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/en_US.yml
@@ -417,8 +417,6 @@ en_US:
ERROR_UNKNOWN: "An unknown file upload error occurred"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEXT"
- TEXT_SWITCHER_RICH_FORMATTING: "Rich formatting"
- TEXT_SWITCHER_CONFIRM: "Text formatting and images will be lost. Are you sure you want to continue?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Personal"
LABEL_GENERAL_NAME: "General"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/es_ES.yml b/rainloop/v/0.0.0/app/localization/webmail/es_ES.yml
index 94ede0012..5e2f174a2 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/es_ES.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/es_ES.yml
@@ -418,8 +418,6 @@ es_ES:
ERROR_UNKNOWN: "Se ha producido un error de carga de archivo desconocido"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEXTO"
- TEXT_SWITCHER_RICH_FORMATTING: "Texto enriquecido"
- TEXT_SWITCHER_CONFIRM: "Se perderá el formato de texto e imágenes. ¿Está seguro que desea continuar?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Personal"
LABEL_GENERAL_NAME: "General"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/fr_FR.yml b/rainloop/v/0.0.0/app/localization/webmail/fr_FR.yml
index 9841946dc..a6a22c2cb 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/fr_FR.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/fr_FR.yml
@@ -418,8 +418,6 @@ fr_FR:
ERROR_UNKNOWN: "Une erreur inconnue de téléversement de fichier s'est produite"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> Non-formaté"
- TEXT_SWITCHER_RICH_FORMATTING: "Format riche"
- TEXT_SWITCHER_CONFIRM: "Le formatage du texte et les images seront perdus. Êtes-vous sûr de vouloir continuer ?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Personnel"
LABEL_GENERAL_NAME: "Général"
diff --git a/rainloop/v/0.0.0/app/localization/webmail/nl_NL.yml b/rainloop/v/0.0.0/app/localization/webmail/nl_NL.yml
index 78900dbb8..d4bee4475 100644
--- a/rainloop/v/0.0.0/app/localization/webmail/nl_NL.yml
+++ b/rainloop/v/0.0.0/app/localization/webmail/nl_NL.yml
@@ -416,8 +416,6 @@ nl_NL:
ERROR_UNKNOWN: "Er is een onbekende fout opgetreden"
EDITOR:
TEXT_SWITCHER_PLAINT_TEXT: "HTML <-> TEKST"
- TEXT_SWITCHER_RICH_FORMATTING: "Tekst met opmaak"
- TEXT_SWITCHER_CONFIRM: "Tekst opmaak zal verloren gaan. Wenst u verder te gaan?"
SETTINGS_LABELS:
LABEL_PERSONAL_NAME: "Persoonlijk"
LABEL_GENERAL_NAME: "Algemeen"
diff --git a/vendors/ckeditor-plugins/plain/plugin.js b/vendors/ckeditor-plugins/plain/plugin.js
index 87783b1f2..a88e9702e 100644
--- a/vendors/ckeditor-plugins/plain/plugin.js
+++ b/vendors/ckeditor-plugins/plain/plugin.js
@@ -27,46 +27,6 @@
if (el) {
el.scrollTop = 0;
}
- },
- simplePlainToHtml = function (sPlain) {
- return sPlain
- .replace(/&/g, '&')
- .replace(/>/g, '>').replace(/')
- .replace(/\n/g, '
')
- .replace(/ /g, ' ')
- ;
- },
- simpleHtmlToPlain = function (sHtml) {
-
- var sText = sHtml
- .replace(/[\s]+/gm, ' ')
- .replace(/
]*>/gmi, '\n')
- .replace(/<\/h[\d]>/gi, '\n')
- .replace(/<\/p>/gi, '\n\n')
- .replace(/<\/li>/gi, '\n')
- .replace(/<\/td>/gi, '\n')
- .replace(/<\/tr>/gi, '\n')
- .replace(/<\/div>/gi, '\n')
- .replace(/
]*>/gmi, '\n') - .replace(/<\/blockquote>/gi, '\n') - .replace(/
]*>/gmi, '\n_______________________________\n\n') - .replace(/ /gi, ' ') - .replace(/"/gi, '"') - .replace(/<[^>]*>/gm, '') - ; - - sText = $('').html(sText).text(); - - sText = sText - .replace(/\n[ \t]+/gm, '\n') - .replace(/[\n]{3,}/gm, '\n\n') - .replace(/>/gi, '>') - .replace(/</gi, '<') - .replace(/&/gi, '&') - ; - - return sText; } ; @@ -81,14 +41,8 @@ } editor.__textUtils = { - plainToHtml: function(data) { - return window.rainloop_Utils_plainToHtml ? - window.rainloop_Utils_plainToHtml(data, true) : simplePlainToHtml(data); - }, - htmlToPlain: function(data) { - return window.rainloop_Utils_htmlToPlain ? - window.rainloop_Utils_htmlToPlain(data, true) : simpleHtmlToPlain(data); - } + plainToHtml: data => rl.Utils.plainToHtml(data, true), + htmlToPlain: data => rl.Utils.htmlToPlain(data, true) }; var plain = CKEDITOR.plugins.plain;