Use less jQuery, more native

This commit is contained in:
djmaze 2020-08-27 15:45:47 +02:00
parent 24cb874c87
commit bdb36ec128
35 changed files with 492 additions and 779 deletions

View file

@ -81,29 +81,29 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
|js/* |1.14.0 |native | |js/* |1.14.0 |native |
|----------- |--------: |--------: | |----------- |--------: |--------: |
|admin.js |2.130.942 |1.007.370 | |admin.js |2.130.942 | 973.239 |
|app.js |4.184.455 |2.676.306 | |app.js |4.184.455 |2.640.205 |
|boot.js | 671.522 | 43.856 | |boot.js | 671.522 | 43.824 |
|libs.js | 647.614 | 316.969 | |libs.js | 647.614 | 316.970 |
|polyfills.js | 325.834 | 0 | |polyfills.js | 325.834 | 0 |
|TOTAL |7.960.367 |4.044.501 | |TOTAL |7.960.367 |3.974.238 |
|js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli | |js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|--------------- |--------: |--------: |--------: |--------: |--------: | |--------------- |--------: |--------: |--------: |--------: |--------: |
|admin.min.js | 252.147 | 138.101 | 73.657 | 40.104 | 34.210 | |admin.min.js | 252.147 | 132.156 | 73.657 | 38.184 | 32.740 |
|app.min.js | 511.202 | 360.198 |140.462 | 95.043 | 76.240 | |app.min.js | 511.202 | 356.109 |140.462 | 93.738 | 75.150 |
|boot.min.js | 66.007 | 5.575 | 22.567 | 2.340 | 2.000 | |boot.min.js | 66.007 | 5.560 | 22.567 | 2.341 | 2.004 |
|libs.min.js | 572.545 | 300.691 |176.720 | 92.925 | 82.046 | |libs.min.js | 572.545 | 300.691 |176.720 | 92.925 | 82.046 |
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 | |polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|TOTAL |1.434.353 | 804.565 |424.718 |230.412 |194.496 | |TOTAL |1.434.353 | 794.516 |424.718 |227.188 |191.940 |
629.788 bytes (194.306 gzip) is not much, but it feels faster. 639.837 bytes (197.530 gzip) is not much, but it feels faster.
|css/* |1.14.0 |native | |css/* |1.14.0 |native |
|-------------- |--------: |--------: | |-------------- |--------: |--------: |
|app.css | 340.334 | 266.769 | |app.css | 340.334 | 266.739 |
|app.min.css | 274.791 | 211.601 | |app.min.css | 274.791 | 211.574 |
### PHP73 branch ### PHP73 branch

View file

@ -72,7 +72,7 @@ class AbstractApp extends AbstractBoot {
} else { } else {
const oLink = document.createElement('a'); const oLink = document.createElement('a');
oLink.href = link; oLink.href = link;
document.body.appendChild(oLink).click(); document.body.append(oLink).click();
oLink.remove(); oLink.remove();
// open(link, '_self'); // open(link, '_self');
} }
@ -165,11 +165,7 @@ class AbstractApp extends AbstractBoot {
ko.components.register('SaveTrigger', require('Component/SaveTrigger').default); ko.components.register('SaveTrigger', require('Component/SaveTrigger').default);
ko.components.register('Input', require('Component/Input').default); ko.components.register('Input', require('Component/Input').default);
ko.components.register('Select', require('Component/Select').default); ko.components.register('Select', require('Component/Select').default);
ko.components.register('Radio', require('Component/Radio').default);
ko.components.register('TextArea', require('Component/TextArea').default); ko.components.register('TextArea', require('Component/TextArea').default);
ko.components.register('Date', require('Component/Date').default);
ko.components.register('x-script', require('Component/Script').default);
if (Settings.appSettingsGet('materialDesign') && !bMobileDevice) { if (Settings.appSettingsGet('materialDesign') && !bMobileDevice) {
ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox').default); ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox').default);

View file

@ -819,7 +819,7 @@ class AppUser extends AbstractApp {
const resizer = doc.createElement('div'), const resizer = doc.createElement('div'),
cssint = s => parseFloat(getComputedStyle(source, null).getPropertyValue(s).replace('px', '')); cssint = s => parseFloat(getComputedStyle(source, null).getPropertyValue(s).replace('px', ''));
resizer.className = 'resizer'; resizer.className = 'resizer';
source.appendChild(resizer); source.append(resizer);
resizer.addEventListener('mousedown', { resizer.addEventListener('mousedown', {
source: source, source: source,
mode: mode, mode: mode,
@ -995,7 +995,7 @@ class AppUser extends AbstractApp {
script.onload = openpgpCallback; script.onload = openpgpCallback;
script.onerror = () => console.error(script.src); script.onerror = () => console.error(script.src);
script.src = openPgpJs(); script.src = openPgpJs();
doc.head.appendChild(script); doc.head.append(script);
} }
} else { } else {
PgpStore.capaOpenPGP(false); PgpStore.capaOpenPGP(false);

View file

@ -45,8 +45,8 @@ function writeCSS(css) {
const style = doc.createElement('style'); const style = doc.createElement('style');
style.type = 'text/css'; style.type = 'text/css';
style.textContent = css; style.textContent = css;
// style.appendChild(doc.createTextNode(styles)); // style.append(doc.createTextNode(styles));
doc.head.appendChild(style); doc.head.append(style);
} }
function loadScript(src) { function loadScript(src) {
@ -59,8 +59,8 @@ function loadScript(src) {
script.onerror = () => reject(new Error(src)); script.onerror = () => reject(new Error(src));
script.src = src; script.src = src;
// script.type = 'text/javascript'; // script.type = 'text/javascript';
doc.head.appendChild(script); doc.head.append(script);
// doc.body.appendChild(element); // doc.body.append(element);
}); });
} }

View file

@ -1,8 +1,8 @@
import ko from 'ko'; import ko from 'ko';
import { KeyState } from 'Common/Enums'; import { KeyState } from 'Common/Enums';
export const $html = jQuery('html'); export const $html = document.documentElement;
export const $htmlCL = document.documentElement.classList; export const $htmlCL = $html.classList;
/** /**
* @type {?} * @type {?}

View file

@ -106,7 +106,6 @@ class HtmlEditor {
this.onModeChange = onModeChange; this.onModeChange = onModeChange;
this.element = element; this.element = element;
this.$element = jQuery(element);
this.resize = this.resizeEditor.throttle(100); this.resize = this.resizeEditor.throttle(100);
@ -414,7 +413,7 @@ class HtmlEditor {
resizeEditor() { resizeEditor() {
if (this.editor && this.__resizable) { if (this.editor && this.__resizable) {
try { try {
this.editor.resize(this.$element.width(), this.$element.innerHeight()); this.editor.resize(this.element.clientWidth, this.element.clientHeight);
} catch (e) {} // eslint-disable-line no-empty } catch (e) {} // eslint-disable-line no-empty
} }
} }

View file

@ -15,7 +15,6 @@ class Selector {
iSelectNextHelper = 0; iSelectNextHelper = 0;
iFocusedNextHelper = 0; iFocusedNextHelper = 0;
oContentVisible;
oContentScrollable; oContentScrollable;
sItemSelector; sItemSelector;
@ -262,12 +261,11 @@ class Selector {
this.focusedItem(null); this.focusedItem(null);
} }
init(contentVisible, contentScrollable, keyScope = 'all') { init(contentScrollable, keyScope = 'all') {
this.oContentVisible = contentVisible; this.oContentScrollable = contentScrollable;
this.oContentScrollable = contentScrollable ? contentScrollable[0] : null;
if (this.oContentVisible && this.oContentScrollable) { if (contentScrollable) {
jQuery(this.oContentVisible) jQuery(contentScrollable)
.on('selectstart', (event) => { .on('selectstart', (event) => {
if (event && event.preventDefault) { if (event && event.preventDefault) {
event.preventDefault(); event.preventDefault();
@ -289,8 +287,9 @@ class Selector {
}); });
key('enter', keyScope, () => { key('enter', keyScope, () => {
if (this.focusedItem() && !this.focusedItem().selected()) { const focused = this.focusedItem();
this.actionClick(this.focusedItem()); if (focused && !focused.selected()) {
this.actionClick(focused);
return false; return false;
} }
@ -500,45 +499,29 @@ class Selector {
* @returns {boolean} * @returns {boolean}
*/ */
scrollToFocused() { scrollToFocused() {
if (!this.oContentVisible || !this.oContentScrollable) { const scrollable = this.oContentScrollable;
return false; if (scrollable) {
let block, focused = scrollable.querySelector(this.sItemFocusedSelector);
if (focused) {
const fRect = focused.getBoundingClientRect(),
sRect = scrollable.getBoundingClientRect();
if (fRect.top < sRect.top) {
block = 'start';
} else if (fRect.bottom > sRect.bottom) {
block = 'end';
}
block && focused.scrollIntoView(block === 'start');
} else {
scrollable.scrollTop = 0;
} }
const offset = 20,
list = this.list(),
$focused = jQuery(this.sItemFocusedSelector, this.oContentScrollable),
pos = $focused.position(),
visibleHeight = this.oContentVisible.height(),
focusedHeight = $focused.outerHeight();
if (list && list[0] && list[0].focused()) {
this.oContentScrollable.scrollTop = 0;
return true;
} else if (pos && (0 > pos.top || pos.top + focusedHeight > visibleHeight)) {
let top = this.oContentScrollable.scrollTop + pos.top;
this.oContentScrollable.scrollTop =
0 > pos.top
? top - offset
: top - visibleHeight + focusedHeight + offset
;
return true;
} }
return false;
} }
/** /**
* @returns {boolean} * @returns {boolean}
*/ */
scrollToTop() { scrollToTop() {
if (!this.oContentVisible || !this.oContentScrollable) { this.oContentScrollable && (this.oContentScrollable.scrollTop = 0);
return false;
}
this.oContentScrollable.scrollTop = 0;
return true;
} }
eventClickFunction(item, event) { eventClickFunction(item, event) {

View file

@ -1,13 +1,12 @@
import ko from 'ko'; import ko from 'ko';
import { Notification, UploadErrorCode } from 'Common/Enums'; import { Notification, UploadErrorCode } from 'Common/Enums';
import { pInt } from 'Common/Utils';
import { $html, $htmlCL } from 'Common/Globals';
import { langLink } from 'Common/Links'; import { langLink } from 'Common/Links';
let I18N_DATA = window.rainloopI18N || {}; let I18N_DATA = window.rainloopI18N || {};
const I18N_NOTIFICATION_DATA = {}; const doc = document,
const I18N_NOTIFICATION_MAP = [ I18N_NOTIFICATION_DATA = {},
I18N_NOTIFICATION_MAP = [
[Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'], [Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'],
[Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'], [Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'],
[Notification.AuthError, 'NOTIFICATIONS/AUTH_ERROR'], [Notification.AuthError, 'NOTIFICATIONS/AUTH_ERROR'],
@ -101,26 +100,24 @@ export function i18n(key, valueList, defaulValue) {
return result; return result;
} }
const i18nToNode = (element) => { const i18nToNode = element => {
const $el = jQuery(element), const key = element.dataset.i18n;
key = $el.data('i18n');
if (key) { if (key) {
if ('[' === key.substr(0, 1)) { if ('[' === key.substr(0, 1)) {
switch (key.substr(0, 6)) { switch (key.substr(0, 6)) {
case '[html]': case '[html]':
$el.html(i18n(key.substr(6))); element.innerHTML = i18n(key.substr(6));
break; break;
case '[place': case '[place':
$el.attr('placeholder', i18n(key.substr(13))); element.placeholder = i18n(key.substr(13));
break; break;
case '[title': case '[title':
$el.attr('title', i18n(key.substr(7))); element.title = i18n(key.substr(7));
break; break;
// no default // no default
} }
} else { } else {
$el.text(i18n(key)); element.textContent = i18n(key);
} }
} }
}; };
@ -129,11 +126,9 @@ const i18nToNode = (element) => {
* @param {Object} elements * @param {Object} elements
* @param {boolean=} animate = false * @param {boolean=} animate = false
*/ */
export function i18nToNodes(elements) { export function i18nToNodes(element) {
setTimeout(() => setTimeout(() =>
jQuery('[data-i18n]', elements).each((index, item) => { element.querySelectorAll('[data-i18n]').forEach(item => i18nToNode(item))
i18nToNode(item);
})
, 1); , 1);
} }
@ -141,7 +136,7 @@ const reloadData = () => {
if (window.rainloopI18N) { if (window.rainloopI18N) {
I18N_DATA = window.rainloopI18N || {}; I18N_DATA = window.rainloopI18N || {};
i18nToNodes(document); i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time')); dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger()); trigger(!trigger());
@ -209,7 +204,7 @@ export function getNotification(code, message = '', defCode = null) {
*/ */
export function getNotificationFromResponse(response, defCode = Notification.UnknownNotification) { export function getNotificationFromResponse(response, defCode = Notification.UnknownNotification) {
return response && response.ErrorCode return response && response.ErrorCode
? getNotification(pInt(response.ErrorCode), response.ErrorMessage || '') ? getNotification(parseInt(response.ErrorCode, 10) || defCode, response.ErrorMessage || '')
: getNotification(defCode); : getNotification(defCode);
} }
@ -253,8 +248,6 @@ export function getUploadErrorDescByCode(code) {
export function reload(admin, language) { export function reload(admin, language) {
const start = Date.now(); const start = Date.now();
$htmlCL.add('rl-changing-language');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return fetch(langLink(language, admin), {cache: 'reload'}) return fetch(langLink(language, admin), {cache: 'reload'})
.then(response => { .then(response => {
@ -269,18 +262,19 @@ export function reload(admin, language) {
reject(new Error(error.message)) reject(new Error(error.message))
}) })
.then(data => { .then(data => {
var script = document.createElement('script'); var script = doc.createElement('script');
script.text = data; script.text = data;
document.head.appendChild(script).parentNode.removeChild(script); doc.head.append(script).remove();
setTimeout( setTimeout(
() => { () => {
reloadData(); reloadData();
const isRtl = ['ar', 'ar_sa', 'he', 'he_he', 'ur', 'ur_ir'].includes((language || '').toLowerCase()); const isRtl = ['ar', 'ar_sa', 'he', 'he_he', 'ur', 'ur_ir'].includes((language || '').toLowerCase()),
htmlCL = doc.documentElement.classList;
$htmlCL.remove('rl-changing-language', 'rl-rtl', 'rl-ltr'); htmlCL.remove('rl-rtl', 'rl-ltr');
// $html.attr('dir', isRtl ? 'rtl' : 'ltr') htmlCL.add(isRtl ? 'rl-rtl' : 'rl-ltr');
$htmlCL.add(isRtl ? 'rl-rtl' : 'rl-ltr'); // doc.documentElement.dir = isRtl ? 'rtl' : 'ltr'
resolve(); resolve();
}, },
@ -289,6 +283,3 @@ export function reload(admin, language) {
}); });
}); });
} }
// init section
$htmlCL.add('rl-' + ($html.attr('dir') || 'ltr'));

View file

@ -3,8 +3,7 @@ import { Mime } from 'Common/Mime';
const const
doc = document, doc = document,
$ = jQuery, tpl = doc.createElement('template'),
$div = $('<div></div>'),
isArray = Array.isArray, isArray = Array.isArray,
htmlmap = { htmlmap = {
'&': '&amp;', '&': '&amp;',
@ -15,6 +14,13 @@ const
}, },
htmlspecialchars = str => (''+str).replace(/[&<>"']/g, m => htmlmap[m]); htmlspecialchars = str => (''+str).replace(/[&<>"']/g, m => htmlmap[m]);
export function htmlToElement(html) {
var template = document.createElement('template');
template.innerHTML = html.trim();
return template.content.firstChild;
}
/** /**
* @param {(string|number)} value * @param {(string|number)} value
* @param {boolean=} includeZero = true * @param {boolean=} includeZero = true
@ -137,7 +143,7 @@ export function inFocus() {
try { try {
if (doc.activeElement) { if (doc.activeElement) {
if (undefined === doc.activeElement.__inFocusCache) { if (undefined === doc.activeElement.__inFocusCache) {
doc.activeElement.__inFocusCache = $(doc.activeElement).is( doc.activeElement.__inFocusCache = doc.activeElement.matches(
'input,textarea,iframe,.cke_editable' 'input,textarea,iframe,.cke_editable'
); );
} }
@ -156,8 +162,7 @@ export function inFocus() {
export function removeInFocus(force) { export function removeInFocus(force) {
if (doc.activeElement && doc.activeElement.blur) { if (doc.activeElement && doc.activeElement.blur) {
try { try {
const activeEl = $(doc.activeElement); if (force || doc.activeElement.matches('input,textarea')) {
if (force || (activeEl && activeEl.is('input,textarea'))) {
doc.activeElement.blur(); doc.activeElement.blur();
} }
} catch (e) {} // eslint-disable-line no-empty } catch (e) {} // eslint-disable-line no-empty
@ -275,19 +280,6 @@ export function convertLangName(language, isEng = false) {
); );
} }
/**
* @returns {object}
*/
export function draggablePlace() {
return $(
'<div class="draggablePlace">' +
'<span class="text"></span>&nbsp;' +
'<i class="icon-copy icon-white visible-on-ctrl"></i>' +
'<i class="icon-mail icon-white hidden-on-ctrl"></i>' +
'</div>'
).appendTo('#rl-hidden');
}
/** /**
* @param {object} domOption * @param {object} domOption
* @param {object} item * @param {object} item
@ -299,65 +291,6 @@ export function defautOptionsAfterRender(domItem, item) {
} }
} }
/**
* @param {string} title
* @param {Object} body
* @param {boolean} isHtml
* @param {boolean} print
*/
export function clearBqSwitcher(body) {
body.find('blockquote.rl-bq-switcher').removeClass('rl-bq-switcher hidden-bq');
body
.find('.rlBlockquoteSwitcher')
.off('.rlBlockquoteSwitcher')
.remove();
body.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper');
}
/**
* @param {object} messageData
* @param {Object} body
* @param {boolean} isHtml
* @param {boolean} print
* @returns {void}
*/
export function previewMessage(
{ title, subject, date, fromCreds, toCreds, toLabel, ccClass, ccCreds, ccLabel },
body,
isHtml,
print
) {
const win = open(''),
doc = win.document,
bodyClone = body.clone(),
bodyClass = isHtml ? 'html' : 'plain';
clearBqSwitcher(bodyClone);
const html = bodyClone ? bodyClone.html() : '';
doc.write(
deModule(require('Html/PreviewMessage.html'))
.replace('{{title}}', encodeHtml(title))
.replace('{{subject}}', encodeHtml(subject))
.replace('{{date}}', encodeHtml(date))
.replace('{{fromCreds}}', encodeHtml(fromCreds))
.replace('{{toCreds}}', encodeHtml(toCreds))
.replace('{{toLabel}}', encodeHtml(toLabel))
.replace('{{ccClass}}', encodeHtml(ccClass))
.replace('{{ccCreds}}', encodeHtml(ccCreds))
.replace('{{ccLabel}}', encodeHtml(ccLabel))
.replace('{{bodyClass}}', bodyClass)
.replace('{{html}}', html)
);
doc.close();
if (print) {
setTimeout(() => win.print(), 100);
}
}
/** /**
* @param {Function} fCallback * @param {Function} fCallback
* @param {?} koTrigger * @param {?} koTrigger
@ -483,7 +416,7 @@ export function htmlToPlain(html) {
fixAttibuteValue = (...args) => (args && 1 < args.length ? '' + args[1] + htmlspecialchars(args[2]) : ''), fixAttibuteValue = (...args) => (args && 1 < args.length ? '' + args[1] + htmlspecialchars(args[2]) : ''),
convertLinks = (...args) => (args && 1 < args.length ? args[1].trim() : ''); convertLinks = (...args) => (args && 1 < args.length ? args[1].trim() : '');
text = html tpl.innerHTML = html
.replace(/<p[^>]*><\/p>/gi, '') .replace(/<p[^>]*><\/p>/gi, '')
.replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gim, convertPre) .replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gim, convertPre)
.replace(/[\s]+/gm, ' ') .replace(/[\s]+/gm, ' ')
@ -507,16 +440,13 @@ export function htmlToPlain(html) {
.replace(/&quot;/gi, '"') .replace(/&quot;/gi, '"')
.replace(/<[^>]*>/gm, ''); .replace(/<[^>]*>/gm, '');
text = $div.html(text).text(); text = splitPlainText(tpl.textContent
text = text
.replace(/\n[ \t]+/gm, '\n') .replace(/\n[ \t]+/gm, '\n')
.replace(/[\n]{3,}/gm, '\n\n') .replace(/[\n]{3,}/gm, '\n\n')
.replace(/&gt;/gi, '>') .replace(/&gt;/gi, '>')
.replace(/&lt;/gi, '<') .replace(/&lt;/gi, '<')
.replace(/&amp;/gi, '&'); .replace(/&amp;/gi, '&')
);
text = splitPlainText(text);
pos = 0; pos = 0;
limit = 800; limit = 800;
@ -530,7 +460,6 @@ export function htmlToPlain(html) {
if ((-1 === iP2 || iP3 < iP2) && iP1 < iP3) { if ((-1 === iP2 || iP3 < iP2) && iP1 < iP3) {
text = text.substring(0, iP1) + convertBlockquote(text.substring(iP1 + 13, iP3)) + text.substring(iP3 + 11); text = text.substring(0, iP1) + convertBlockquote(text.substring(iP1 + 13, iP3)) + text.substring(iP3 + 11);
pos = 0; pos = 0;
} else if (-1 < iP2 && iP2 < iP3) { } else if (-1 < iP2 && iP2 < iP3) {
pos = iP2 - 1; pos = iP2 - 1;
@ -552,7 +481,7 @@ export function htmlToPlain(html) {
* @param {boolean} findEmailAndLinksInText = false * @param {boolean} findEmailAndLinksInText = false
* @returns {string} * @returns {string}
*/ */
export function plainToHtml(plain, findEmailAndLinksInText = false) { export function plainToHtml(plain) {
plain = plain.toString().replace(/\r/g, ''); plain = plain.toString().replace(/\r/g, '');
plain = plain.replace(/^>[> ]>+/gm, ([match]) => (match ? match.replace(/[ ]+/g, '') : match)); plain = plain.replace(/^>[> ]>+/gm, ([match]) => (match ? match.replace(/[ ]+/g, '') : match));
@ -595,9 +524,7 @@ export function plainToHtml(plain, findEmailAndLinksInText = false) {
aText = aNextText; aText = aNextText;
} while (bDo); } while (bDo);
plain = aText.join('\n'); return aText.join('\n')
plain = plain
// .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n') // .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
@ -605,8 +532,6 @@ export function plainToHtml(plain, findEmailAndLinksInText = false) {
.replace(/~~~blockquote~~~[\s]*/g, '<blockquote>') .replace(/~~~blockquote~~~[\s]*/g, '<blockquote>')
.replace(/[\s]*~~~\/blockquote~~~/g, '</blockquote>') .replace(/[\s]*~~~\/blockquote~~~/g, '</blockquote>')
.replace(/\n/g, '<br />'); .replace(/\n/g, '<br />');
return findEmailAndLinksInText ? findEmailAndLinks(plain) : plain;
} }
window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation
@ -766,39 +691,6 @@ export function selectElement(element) {
sel.addRange(range); sel.addRange(range);
} }
/**
* @param {boolean=} delay = false
*/
export function triggerAutocompleteInputChange(delay = false) {
const fFunc = () => $('.checkAutocomplete').trigger('change');
if (delay) {
setTimeout(fFunc, 100);
} else {
fFunc();
}
}
const configurationScriptTagCache = {};
/**
* @param {string} configuration
* @returns {object}
*/
export function getConfigurationFromScriptTag(configuration) {
if (!configurationScriptTagCache[configuration]) {
configurationScriptTagCache[configuration] = $(
'script[type="application/json"][data-configuration="' + configuration + '"]'
);
}
try {
return JSON.parse(configurationScriptTagCache[configuration].text());
} catch (e) {} // eslint-disable-line no-empty
return {};
}
/** /**
* @param {Object|Array} objectOrObjects * @param {Object|Array} objectOrObjects
* @returns {void} * @returns {void}

View file

@ -30,7 +30,7 @@ const componentExportHelper = (ClassObject, templateID = '') => ({
params.component = componentInfo; params.component = componentInfo;
params.element = jQuery(componentInfo.element); params.element = jQuery(componentInfo.element);
i18nToNodes(params.element); i18nToNodes(componentInfo.element);
if (undefined !== params.inline && ko.unwrap(params.inline)) { if (undefined !== params.inline && ko.unwrap(params.inline)) {
params.element.css('display', 'inline-block'); params.element.css('display', 'inline-block');

19
dev/External/ko.js vendored
View file

@ -2,6 +2,7 @@ import { SaveSettingsStep } from 'Common/Enums';
const const
$ = jQuery, $ = jQuery,
doc = document,
ko = window.ko, ko = window.ko,
Translator = () => require('Common/Translator'), Translator = () => require('Common/Translator'),
isFunction = v => typeof v === 'function'; isFunction = v => typeof v === 'function';
@ -75,7 +76,7 @@ ko.bindingHandlers.tooltip = {
ko.bindingHandlers.tooltipErrorTip = { ko.bindingHandlers.tooltipErrorTip = {
init: element => { init: element => {
document.addEventListener('click', () => element.removeAttribute('data-rainloopErrorTip')); doc.addEventListener('click', () => element.removeAttribute('data-rainloopErrorTip'));
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeAttribute('data-rainloopErrorTip')); ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeAttribute('data-rainloopErrorTip'));
}, },
update: (element, fValueAccessor) => { update: (element, fValueAccessor) => {
@ -197,13 +198,13 @@ ko.bindingHandlers.modal = {
}); });
}, },
update: (element, fValueAccessor) => { update: (element, fValueAccessor) => {
const Globals = require('Common/Globals'); const htmlCL = doc.documentElement.classList;
$(element).modal(ko.unwrap(fValueAccessor()) ? 'show' : 'hide'); $(element).modal(ko.unwrap(fValueAccessor()) ? 'show' : 'hide');
if (Globals.$htmlCL.contains('no-mobile')) { if (htmlCL.contains('no-mobile')) {
Globals.$htmlCL.add('rl-modal-animation'); htmlCL.add('rl-modal-animation');
setTimeout(() => Globals.$htmlCL.remove('rl-modal-animation'), 500); setTimeout(() => htmlCL.remove('rl-modal-animation'), 500);
} }
} }
}; };
@ -226,11 +227,11 @@ ko.bindingHandlers.i18nUpdate = {
}; };
ko.bindingHandlers.link = { ko.bindingHandlers.link = {
update: (element, fValueAccessor) => element.setAttribute('href', ko.unwrap(fValueAccessor())) update: (element, fValueAccessor) => element.href = ko.unwrap(fValueAccessor())
}; };
ko.bindingHandlers.title = { ko.bindingHandlers.title = {
update: (element, fValueAccessor) => element.setAttribute('title', ko.unwrap(fValueAccessor())) update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor())
}; };
ko.bindingHandlers.initDom = { ko.bindingHandlers.initDom = {
@ -247,7 +248,7 @@ ko.bindingHandlers.draggable = {
scrollSpeed = 3, scrollSpeed = 3,
fAllValueFunc = fAllBindingsAccessor(), fAllValueFunc = fAllBindingsAccessor(),
selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '', selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '',
droppable = selector ? document.querySelector(selector) : null, droppable = selector ? doc.querySelector(selector) : null,
conf = { conf = {
distance: 20, distance: 20,
handle: '.dragHandle', handle: '.dragHandle',
@ -352,7 +353,7 @@ ko.bindingHandlers.saveTrigger = {
$el.data( $el.data(
'save-trigger-type', 'save-trigger-type',
$el.is('input[type=text],input[type=email],input[type=password],select,textarea') ? 'input' : 'custom' element.matches('input[type=text],input[type=email],input[type=password],select,textarea') ? 'input' : 'custom'
); );
if ('custom' === $el.data('save-trigger-type')) { if ('custom' === $el.data('save-trigger-type')) {

View file

@ -53,7 +53,7 @@ export class AbstractViewNext {
closeCommand() {} // eslint-disable-line no-empty-function closeCommand() {} // eslint-disable-line no-empty-function
querySelector(selectors) { querySelector(selectors) {
return this.viewModelDom[0].querySelector(selectors); return this.viewModelDom.querySelector(selectors);
} }
} }

View file

@ -7,7 +7,8 @@ import { $htmlCL, VIEW_MODELS } from 'Common/Globals';
let currentScreen = null, let currentScreen = null,
defaultScreenName = ''; defaultScreenName = '';
const SCREENS = {}, $ = jQuery, const SCREENS = {},
qs = s => document.querySelector(s),
isNonEmptyArray = values => Array.isArray(values) && values.length, isNonEmptyArray = values => Array.isArray(values) && values.length,
popupVisibilityNames = ko.observableArray([]); popupVisibilityNames = ko.observableArray([]);
@ -29,10 +30,8 @@ export const ViewType = {
* @returns {void} * @returns {void}
*/ */
export function hideLoading() { export function hideLoading() {
$('#rl-content').addClass('rl-content-show'); qs('#rl-content').classList.add('rl-content-show');
$('#rl-loading') qs('#rl-loading').remove();
.hide()
.remove();
} }
/** /**
@ -156,7 +155,7 @@ export function buildViewModel(ViewModelClass, vmScreen) {
let vmDom = null; let vmDom = null;
const vm = new ViewModelClass(vmScreen), const vm = new ViewModelClass(vmScreen),
position = ViewModelClass.__type || '', position = ViewModelClass.__type || '',
vmPlace = position ? $('#rl-content #rl-' + position.toLowerCase()) : null; vmPlace = position ? qs('#rl-content #rl-' + position.toLowerCase()) : null;
ViewModelClass.__builded = true; ViewModelClass.__builded = true;
ViewModelClass.__vm = vm; ViewModelClass.__vm = vm;
@ -166,15 +165,14 @@ export function buildViewModel(ViewModelClass, vmScreen) {
vm.viewModelTemplateID = ViewModelClass.__templateID; vm.viewModelTemplateID = ViewModelClass.__templateID;
vm.viewModelPosition = ViewModelClass.__type; vm.viewModelPosition = ViewModelClass.__type;
if (vmPlace && 1 === vmPlace.length) { if (vmPlace) {
vmDom = $('<div></div>') vmDom = jQuery('<div></div>');
.addClass('rl-view-model') vmDom[0].classList.add('rl-view-model', 'RL-' + vm.viewModelTemplateID);
.addClass('RL-' + vm.viewModelTemplateID) vmDom[0].hidden = true;
.hide(); vmPlace.append(vmDom[0]);
vmDom.appendTo(vmPlace);
vm.viewModelDom = vmDom; vm.viewModelDom = vmDom[0];
ViewModelClass.__dom = vmDom; ViewModelClass.__dom = vmDom[0];
if (ViewType.Popup === position) { if (ViewType.Popup === position) {
vm.cancelCommand = vm.closeCommand = createCommand(() => { vm.cancelCommand = vm.closeCommand = createCommand(() => {
@ -183,11 +181,11 @@ export function buildViewModel(ViewModelClass, vmScreen) {
vm.modalVisibility.subscribe((value) => { vm.modalVisibility.subscribe((value) => {
if (value) { if (value) {
vm.viewModelDom.show(); vm.viewModelDom.hidden = false;
vm.storeAndSetKeyScope(); vm.storeAndSetKeyScope();
popupVisibilityNames.push(vm.viewModelName); popupVisibilityNames.push(vm.viewModelName);
vm.viewModelDom.css('z-index', 3000 + popupVisibilityNames().length + 10); vm.viewModelDom.style.zIndex = 3000 + popupVisibilityNames().length + 10;
vm.onShowWithDelay && setTimeout(()=>vm.onShowWithDelay, 500); vm.onShowWithDelay && setTimeout(()=>vm.onShowWithDelay, 500);
} else { } else {
@ -197,9 +195,9 @@ export function buildViewModel(ViewModelClass, vmScreen) {
vm.restoreKeyScope(); vm.restoreKeyScope();
popupVisibilityNames.remove(vm.viewModelName); popupVisibilityNames.remove(vm.viewModelName);
vm.viewModelDom.css('z-index', 2000); vm.viewModelDom.style.zIndex = 2000;
setTimeout(() => vm.viewModelDom.hide(), 300); setTimeout(() => vm.viewModelDom.hidden = true, 300);
} }
}); });
} }
@ -245,7 +243,7 @@ export function showScreenPopup(ViewModelClassToShow, params = []) {
ModalView.__vm.onShow && ModalView.__vm.onShow(...params); ModalView.__vm.onShow && ModalView.__vm.onShow(...params);
// if (!bMobileDevice) { // if (!bMobileDevice) {
const af = ModalView.__dom[0].querySelector('[autofocus]'); const af = ModalView.__dom.querySelector('[autofocus]');
af && af.focus(); af && af.focus();
} }
} }
@ -327,7 +325,7 @@ export function screenOnRoute(screenName, subPart) {
ViewModelClass.__dom && ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) { ) {
ViewModelClass.__dom.hide(); ViewModelClass.__dom.hidden = true;
ViewModelClass.__vm.viewModelVisibility(false); ViewModelClass.__vm.viewModelVisibility(false);
ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide(); ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide();
@ -353,13 +351,13 @@ export function screenOnRoute(screenName, subPart) {
) { ) {
ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow(); ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow();
ViewModelClass.__dom.show(); ViewModelClass.__dom.hidden = false;
ViewModelClass.__vm.viewModelVisibility(true); ViewModelClass.__vm.viewModelVisibility(true);
ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow(); ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow();
// if (!bMobileDevice) { // if (!bMobileDevice) {
const af = ViewModelClass.__dom[0].querySelector('[autofocus]'); const af = ViewModelClass.__dom.querySelector('[autofocus]');
af && af.focus(); af && af.focus();
ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200); ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200);

View file

@ -5,7 +5,8 @@ import { i18n } from 'Common/Translator';
import { import {
pInt, pInt,
previewMessage, deModule,
encodeHtml,
friendlySize, friendlySize,
isNonEmptyArray isNonEmptyArray
} from 'Common/Utils'; } from 'Common/Utils';
@ -20,7 +21,7 @@ import { emailArrayFromJson, emailArrayToStringClear, emailArrayToString, replyH
import { AttachmentModel, staticCombinedIconClass } from 'Model/Attachment'; import { AttachmentModel, staticCombinedIconClass } from 'Model/Attachment';
import { AbstractModel } from 'Knoin/AbstractModel'; import { AbstractModel } from 'Knoin/AbstractModel';
const $ = jQuery, isArray = Array.isArray; const isArray = Array.isArray;
class MessageModel extends AbstractModel { class MessageModel extends AbstractModel {
constructor() { constructor() {
@ -604,13 +605,6 @@ class MessageModel extends AbstractModel {
return [toResult, ccResult]; return [toResult, ccResult];
} }
/**
* @returns {string}
*/
textBodyToString() {
return this.body ? this.body.html() : '';
}
/** /**
* @returns {string} * @returns {string}
*/ */
@ -625,24 +619,30 @@ class MessageModel extends AbstractModel {
viewPopupMessage(print = false) { viewPopupMessage(print = false) {
const timeStampInUTC = this.dateTimeStampInUTC() || 0, const timeStampInUTC = this.dateTimeStampInUTC() || 0,
ccLine = this.ccToLine(false), ccLine = this.ccToLine(false),
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null; m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
win = open(''),
doc = win.document;
previewMessage( doc.write(
{ deModule(require('Html/PreviewMessage.html'))
title: this.subject(), .replace('{{title}}', encodeHtml(this.subject()))
subject: this.subject(), .replace('{{subject}}', encodeHtml(this.subject()))
date: m ? m.format('LLL') : '', .replace('{{date}}', encodeHtml(m ? m.format('LLL') : ''))
fromCreds: this.fromToLine(false), .replace('{{fromCreds}}', encodeHtml(this.fromToLine(false)))
toLabel: i18n('MESSAGE/LABEL_TO'), .replace('{{toCreds}}', encodeHtml(this.toToLine(false)))
toCreds: this.toToLine(false), .replace('{{toLabel}}', encodeHtml(i18n('MESSAGE/LABEL_TO')))
ccClass: ccLine ? '' : 'rl-preview-hide', .replace('{{ccClass}}', encodeHtml(ccLine ? '' : 'rl-preview-hide'))
ccLabel: i18n('MESSAGE/LABEL_CC'), .replace('{{ccCreds}}', encodeHtml(ccLine))
ccCreds: ccLine .replace('{{ccLabel}}', encodeHtml(i18n('MESSAGE/LABEL_CC')))
}, .replace('{{bodyClass}}', this.isHtml() ? 'html' : 'plain')
this.body, .replace('{{html}}', this.bodyAsHTML())
this.isHtml(),
print
); );
doc.close();
if (print) {
setTimeout(() => win.print(), 100);
}
} }
printMessage() { printMessage() {
@ -725,74 +725,60 @@ class MessageModel extends AbstractModel {
return this; return this;
} }
showExternalImages(lazy = false) { showExternalImages() {
if (this.body && this.body.data('rl-has-images')) { if (this.body && this.body.rlHasImages) {
this.hasImages(false); this.hasImages(false);
this.body.data('rl-has-images', false); this.body.rlHasImages = false;
let attr = this.proxy ? 'data-x-additional-src' : 'data-x-src'; let body = this.body, attr = this.proxy ? 'data-x-additional-src' : 'data-x-src';
$('[' + attr + ']', this.body).each(function() { body.querySelectorAll('[' + attr + ']').forEach(node => {
const $this = $(this); // eslint-disable-line no-invalid-this if (node.matches('img')) {
if (lazy && $this.is('img')) { node.loading = 'lazy';
$this.attr('loading', 'lazy');
} }
$this.attr('src', $this.attr(attr)).removeAttr('data-loaded'); node.src = node.getAttribute(attr);
node.removeAttribute('data-loaded');
}); });
attr = this.proxy ? 'data-x-additional-style-url' : 'data-x-style-url'; attr = this.proxy ? 'data-x-additional-style-url' : 'data-x-style-url';
$('[' + attr + ']', this.body).each(function() { body.querySelectorAll('[' + attr + ']').forEach(node => {
const $this = $(this); // eslint-disable-line no-invalid-this node.setAttribute('style', ((node.getAttribute('style')||'')
let style = $this.attr('style').trim(); + ';' + node.getAttribute(attr))
style = style ? (';' === style.substr(-1) ? style + ' ' : style + '; ') : ''; .replace(/^[;\s]+/,''));
$this.attr('style', style + $this.attr(attr));
}); });
} }
} }
showInternalImages(lazy = false) { showInternalImages() {
if (this.body && !this.body.data('rl-init-internal-images')) { if (this.body && !this.body.rlInitInternalImages) {
this.body.data('rl-init-internal-images', true); this.body.rlInitInternalImages = true;
const self = this; const body = this.body;
$('[data-x-src-cid]', this.body).each(function() {
const $this = $(this), // eslint-disable-line no-invalid-this
attachment = self.findAttachmentByCid($this.attr('data-x-src-cid'));
body.querySelectorAll('[data-x-src-cid]').forEach(node => {
const attachment = this.findAttachmentByCid(node.dataset.xSrcCid);
if (attachment && attachment.download) { if (attachment && attachment.download) {
$this.attr('src', attachment.linkPreview()); node.src = attachment.linkPreview();
} }
}); });
$('[data-x-src-location]', this.body).each(function() { body.querySelectorAll('[data-x-src-location]').forEach(node => {
const $this = $(this); // eslint-disable-line no-invalid-this const attachment = this.findAttachmentByContentLocation(node.dataset.xSrcLocation)
let attachment = self.findAttachmentByContentLocation($this.attr('data-x-src-location')); || this.findAttachmentByCid(node.dataset.xSrcLocation);
if (!attachment) {
attachment = self.findAttachmentByCid($this.attr('data-x-src-location'));
}
if (attachment && attachment.download) { if (attachment && attachment.download) {
if (lazy && $this.is('img')) { if (node.matches('img')) {
$this.attr('loading', 'lazy'); node.loading = 'lazy';
} }
$this.attr('src', attachment.linkPreview()); node.src = attachment.linkPreview();
} }
}); });
$('[data-x-style-cid]', this.body).each(function() { body.querySelectorAll('[style-cid]').forEach(node => {
let style = '', const name = node.dataset.xStyleCidName,
name = ''; attachment = this.findAttachmentByCid(node.dataset.xStyleCid);
if (attachment && attachment.linkPreview && name) {
const $this = $(this), // eslint-disable-line no-invalid-this node.setAttribute('style', ((node.getAttribute('style')||'')
attachment = self.findAttachmentByCid($this.attr('data-x-style-cid')); + ';' + name + ": url('" + attachment.linkPreview() + "')")
.replace(/^[;\s]+/,''));
if (attachment && attachment.linkPreview) {
name = $this.attr('data-x-style-cid-name');
if (name) {
style = $this.attr('style').trim();
style = style ? (';' === style.substr(-1) ? style + ' ' : style + '; ') : '';
$this.attr('style', style + name + ": url('" + attachment.linkPreview() + "')");
}
} }
}); });
} }
@ -800,22 +786,37 @@ class MessageModel extends AbstractModel {
storeDataInDom() { storeDataInDom() {
if (this.body) { if (this.body) {
this.body.data('rl-is-html', !!this.isHtml()); this.body.rlIsHtml = !!this.isHtml();
this.body.data('rl-has-images', !!this.hasImages()); this.body.rlHasImages = !!this.hasImages();
} }
} }
fetchDataFromDom() { fetchDataFromDom() {
if (this.body) { if (this.body) {
this.isHtml(!!this.body.data('rl-is-html')); this.isHtml(!!this.body.rlIsHtml);
this.hasImages(!!this.body.data('rl-has-images')); this.hasImages(!!this.body.rlHasImages);
} }
} }
replacePlaneTextBody(plain) { /**
* @returns {string}
*/
bodyAsHTML() {
if (this.body) { if (this.body) {
this.body.html(plain).addClass('b-text-part plain'); let clone = this.body.cloneNode(true),
attr = 'data-html-editor-font-wrapper';
clone.querySelectorAll('blockquote.rl-bq-switcher').forEach(
node => node.classList.remove('rl-bq-switcher','hidden-bq')
);
clone.querySelectorAll('.rlBlockquoteSwitcher').forEach(
node => node.remove()
);
clone.querySelectorAll('['+attr+']').forEach(
node => node.removeAttribute(attr)
);
return clone.innerHTML;
} }
return '';
} }
/** /**

View file

@ -116,7 +116,6 @@ class AbstractAjaxRemote {
* @param {?number=} iTimeOut = 20000 * @param {?number=} iTimeOut = 20000
* @param {string=} sGetAdd = '' * @param {string=} sGetAdd = ''
* @param {Array=} aAbortActions = [] * @param {Array=} aAbortActions = []
* @returns {jQuery.jqXHR}
*/ */
ajaxRequest(fResultCallback, params, iTimeOut = 20000, sGetAdd = '', abortActions = []) { ajaxRequest(fResultCallback, params, iTimeOut = 20000, sGetAdd = '', abortActions = []) {
params = params || {}; params = params || {};

View file

@ -34,7 +34,6 @@ class AbstractSettingsScreen extends AbstractScreen {
onRoute(subName) { onRoute(subName) {
let settingsScreen = null, let settingsScreen = null,
RoutedSettingsViewModel = null, RoutedSettingsViewModel = null,
viewModelPlace = null,
viewModelDom = null; viewModelDom = null;
RoutedSettingsViewModel = VIEW_MODELS.settings.find( RoutedSettingsViewModel = VIEW_MODELS.settings.find(
@ -67,20 +66,19 @@ class AbstractSettingsScreen extends AbstractScreen {
if (RoutedSettingsViewModel.__builded && RoutedSettingsViewModel.__vm) { if (RoutedSettingsViewModel.__builded && RoutedSettingsViewModel.__vm) {
settingsScreen = RoutedSettingsViewModel.__vm; settingsScreen = RoutedSettingsViewModel.__vm;
} else { } else {
viewModelPlace = this.oViewModelPlace; if (this.oViewModelPlace) {
if (viewModelPlace && 1 === viewModelPlace.length) {
settingsScreen = new RoutedSettingsViewModel(); settingsScreen = new RoutedSettingsViewModel();
viewModelDom = jQuery('<div></div>') viewModelDom = jQuery('<div></div>');
.addClass('rl-settings-view-model') viewModelDom[0].classList.add('rl-settings-view-model');
.hide(); viewModelDom[0].hidden = true;
viewModelDom.appendTo(viewModelPlace); this.oViewModelPlace.append(viewModelDom[0]);
settingsScreen.viewModelDom = viewModelDom; settingsScreen.viewModelDom = viewModelDom[0];
settingsScreen.__rlSettingsData = RoutedSettingsViewModel.__rlSettingsData; settingsScreen.__rlSettingsData = RoutedSettingsViewModel.__rlSettingsData;
RoutedSettingsViewModel.__dom = viewModelDom; RoutedSettingsViewModel.__dom = viewModelDom[0];
RoutedSettingsViewModel.__builded = true; RoutedSettingsViewModel.__builded = true;
RoutedSettingsViewModel.__vm = settingsScreen; RoutedSettingsViewModel.__vm = settingsScreen;
@ -106,7 +104,7 @@ class AbstractSettingsScreen extends AbstractScreen {
// hide // hide
if (o.oCurrentSubScreen) { if (o.oCurrentSubScreen) {
o.oCurrentSubScreen.onHide && o.oCurrentSubScreen.onHide(); o.oCurrentSubScreen.onHide && o.oCurrentSubScreen.onHide();
o.oCurrentSubScreen.viewModelDom.hide(); o.oCurrentSubScreen.viewModelDom.hidden = true;
} }
// -- // --
@ -115,7 +113,7 @@ class AbstractSettingsScreen extends AbstractScreen {
// show // show
if (o.oCurrentSubScreen) { if (o.oCurrentSubScreen) {
o.oCurrentSubScreen.onBeforeShow && o.oCurrentSubScreen.onBeforeShow(); o.oCurrentSubScreen.onBeforeShow && o.oCurrentSubScreen.onBeforeShow();
o.oCurrentSubScreen.viewModelDom.show(); o.oCurrentSubScreen.viewModelDom.hidden = false;
o.oCurrentSubScreen.onShow && o.oCurrentSubScreen.onShow(); o.oCurrentSubScreen.onShow && o.oCurrentSubScreen.onShow();
o.oCurrentSubScreen.onShowWithDelay && setTimeout(() => o.oCurrentSubScreen.onShowWithDelay(), 200); o.oCurrentSubScreen.onShowWithDelay && setTimeout(() => o.oCurrentSubScreen.onShowWithDelay(), 200);
@ -127,7 +125,7 @@ class AbstractSettingsScreen extends AbstractScreen {
); );
}); });
jQuery('#rl-content .b-settings .b-content')[0].scrollTop = 0; document.querySelector('#rl-content .b-settings .b-content').scrollTop = 0;
} }
// -- // --
}, 1); }, 1);
@ -140,7 +138,7 @@ class AbstractSettingsScreen extends AbstractScreen {
onHide() { onHide() {
if (this.oCurrentSubScreen && this.oCurrentSubScreen.viewModelDom) { if (this.oCurrentSubScreen && this.oCurrentSubScreen.viewModelDom) {
this.oCurrentSubScreen.onHide && this.oCurrentSubScreen.onHide(); this.oCurrentSubScreen.onHide && this.oCurrentSubScreen.onHide();
this.oCurrentSubScreen.viewModelDom.hide(); this.oCurrentSubScreen.viewModelDom.hidden = true;
} }
} }
@ -164,7 +162,7 @@ class AbstractSettingsScreen extends AbstractScreen {
} }
}); });
this.oViewModelPlace = jQuery('#rl-content #rl-settings-subscreen'); this.oViewModelPlace = document.getElementById('rl-settings-subscreen');
} }
routes() { routes() {

View file

@ -128,7 +128,7 @@ class MailBoxUserScreen extends AbstractScreen {
, 1); , 1);
} }
$html.on('click', '#rl-right', () => { jQuery($html).on('click', '#rl-right', () => {
moveAction(false); moveAction(false);
}); });
} }

View file

@ -6,7 +6,8 @@ import {
pInt, pInt,
pString, pString,
plainToHtml, plainToHtml,
findEmailAndLinks findEmailAndLinks,
htmlToElement
} from 'Common/Utils'; } from 'Common/Utils';
import { import {
@ -44,25 +45,17 @@ import { getApp } from 'Helper/Apps/User';
import Remote from 'Remote/User/Ajax'; import Remote from 'Remote/User/Ajax';
const const
$ = jQuery, hcont = htmlToElement('<div area="hidden" style="position:absolute;left:-5000px"></div>'),
$div = $('<div></div>'), getRealHeight = el => {
$hcont = $('<div></div>'), hcont.innerHTML = el.outerHTML;
getRealHeight = $el => { const result = hcont.clientHeight;
$el hcont.innerHTML = '';
.clone()
.show()
.appendTo($hcont);
const result = $hcont.height();
$hcont.empty();
return result; return result;
}; };
let iMessageBodyCacheCount = 0; let iMessageBodyCacheCount = 0;
$hcont document.body.append(hcont);
.attr('area', 'hidden')
.css({ position: 'absolute', left: -5000 })
.appendTo($('body'));
class MessageUserStore { class MessageUserStore {
constructor() { constructor() {
@ -218,12 +211,6 @@ class MessageUserStore {
this.messageLoading.subscribe(value => this.messageLoadingThrottle(value)); this.messageLoading.subscribe(value => this.messageLoadingThrottle(value));
this.messagesBodiesDom.subscribe((dom) => {
if (dom && !(dom instanceof $)) {
this.messagesBodiesDom($(dom));
}
});
this.messageListEndFolder.subscribe(folder => { this.messageListEndFolder.subscribe(folder => {
const message = this.message(); const message = this.message();
if (message && folder && folder !== message.folderFullNameRaw) { if (message && folder && folder !== message.folderFullNameRaw) {
@ -233,22 +220,20 @@ class MessageUserStore {
} }
purgeMessageBodyCache() { purgeMessageBodyCache() {
let count = 0;
const end = iMessageBodyCacheCount - MESSAGE_BODY_CACHE_LIMIT; const end = iMessageBodyCacheCount - MESSAGE_BODY_CACHE_LIMIT;
if (0 < end) { if (0 < end) {
let count = 0;
const messagesDom = this.messagesBodiesDom(); const messagesDom = this.messagesBodiesDom();
if (messagesDom) { if (messagesDom) {
messagesDom.find('.rl-cache-class').each(function() { messagesDom.querySelectorAll('.rl-cache-class').forEach(node => {
const item = $(this); // eslint-disable-line no-invalid-this if (end > node.rlCacheCount) {
if (end > item.data('rl-cache-count')) { node.classList.add('rl-cache-purge');
item.addClass('rl-cache-purge'); ++count;
count += 1;
} }
}); });
if (0 < count) { if (0 < count) {
setTimeout(() => messagesDom.find('.rl-cache-purge').remove(), 350); setTimeout(() => messagesDom.querySelectorAll('.rl-cache-purge').forEach(node => node.remove()), 350);
} }
} }
} }
@ -291,9 +276,7 @@ class MessageUserStore {
hideMessageBodies() { hideMessageBodies() {
const messagesDom = this.messagesBodiesDom(); const messagesDom = this.messagesBodiesDom();
if (messagesDom) { messagesDom && messagesDom.querySelectorAll('.b-text-part').forEach(el => el.hidden = true);
messagesDom.find('.b-text-part').hide();
}
} }
/** /**
@ -441,42 +424,27 @@ class MessageUserStore {
* @param {Object} messageTextBody * @param {Object} messageTextBody
*/ */
initBlockquoteSwitcher(messageTextBody) { initBlockquoteSwitcher(messageTextBody) {
if (messageTextBody) { messageTextBody && messageTextBody.querySelectorAll('blockquote:not(.rl-bq-switcher)').forEach(node => {
const $oList = $('blockquote:not(.rl-bq-switcher)', messageTextBody).filter(function() { if (node.textContent.trim() && !node.parentNode.closest('blockquote')) {
return !$(this).parent().closest('blockquote', messageTextBody).length; let h = node.clientHeight || getRealHeight(node);
}); if (0 === h || 100 < h) {
const el = htmlToElement('<span class="rlBlockquoteSwitcher">•••</span>');
if ($oList && $oList.length) { node.classList.add('rl-bq-switcher','hidden-bq');
$oList.each(function() { node.before(el);
const $this = $(this); // eslint-disable-line no-invalid-this el.addEventListener('click', () => node.classList.toggle('hidden-bq'));
}
let h = $this.height() || getRealHeight($this);
if ($this.text().trim() && (0 === h || 100 < h)) {
$this.addClass('rl-bq-switcher hidden-bq');
$('<span class="rlBlockquoteSwitcher"><i class="icon-ellipsis" /></span>')
.insertBefore($this)
.on('click.rlBlockquoteSwitcher', () => {
$this.toggleClass('hidden-bq');
})
.after('<br />')
.before('<br />');
} }
}); });
} }
}
}
/** /**
* @param {Object} messageTextBody * @param {Object} messageTextBody
* @param {Object} message * @param {Object} message
*/ */
initOpenPgpControls(messageTextBody, message) { initOpenPgpControls(messageTextBody, message) {
if (messageTextBody && messageTextBody.find) { messageTextBody && messageTextBody.querySelectorAll('.b-plain-openpgp:not(.inited)').forEach(node =>
messageTextBody.find('.b-plain-openpgp:not(.inited)').each(function() { PgpStore.initMessageBodyControls(node, message)
PgpStore.initMessageBodyControls($(this), message); // eslint-disable-line no-invalid-this );
});
}
} }
setMessage(data, cached) { setMessage(data, cached) {
@ -521,19 +489,22 @@ class MessageUserStore {
message.initFlagsByJson(data.Result); message.initFlagsByJson(data.Result);
} }
messagesDom = messagesDom && messagesDom[0] ? messagesDom : null;
if (messagesDom) { if (messagesDom) {
id = 'rl-mgs-' + message.hash.replace(/[^a-zA-Z0-9]/g, ''); id = 'rl-mgs-' + message.hash.replace(/[^a-zA-Z0-9]/g, '');
const textBody = messagesDom.find('#' + id); const textBody = document.getElementById(id);
if (!textBody || !textBody[0]) { if (textBody) {
iMessageBodyCacheCount += 1;
textBody.rlCacheCount = iMessageBodyCacheCount;
message.fetchDataFromDom();
} else {
let isHtml = false; let isHtml = false;
if (data.Result.Html) { if (data.Result.Html) {
isHtml = true; isHtml = true;
resultHtml = data.Result.Html.toString(); resultHtml = data.Result.Html.toString();
} else if (data.Result.Plain) { } else if (data.Result.Plain) {
isHtml = false; isHtml = false;
resultHtml = plainToHtml(data.Result.Plain.toString(), false); resultHtml = plainToHtml(data.Result.Plain.toString());
if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpStore.capaOpenPGP()) { if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpStore.capaOpenPGP()) {
plain = pString(data.Result.Plain); plain = pString(data.Result.Plain);
@ -544,16 +515,17 @@ class MessageUserStore {
/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain); /-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain);
} }
$div.empty(); const pre = document.createElement('pre');
if (pgpSigned && message.isPgpSigned()) { if (pgpSigned && message.isPgpSigned()) {
resultHtml = $div.append($('<pre class="b-plain-openpgp signed"></pre>').text(plain)).html(); pre.className = 'b-plain-openpgp signed';
pre.textContent = plain;
} else if (isPgpEncrypted && message.isPgpEncrypted()) { } else if (isPgpEncrypted && message.isPgpEncrypted()) {
resultHtml = $div.append($('<pre class="b-plain-openpgp encrypted"></pre>').text(plain)).html(); pre.className = 'b-plain-openpgp encrypted';
pre.textContent = plain;
} else { } else {
resultHtml = '<pre>' + resultHtml + '</pre>'; pre.innerHTML = resultHtml;
} }
resultHtml = pre.outerHTML;
$div.empty();
message.isPgpSigned(pgpSigned); message.isPgpSigned(pgpSigned);
message.isPgpEncrypted(isPgpEncrypted); message.isPgpEncrypted(isPgpEncrypted);
@ -567,41 +539,31 @@ class MessageUserStore {
iMessageBodyCacheCount += 1; iMessageBodyCacheCount += 1;
body = $('<div id="' + id + '" ></div>') body = htmlToElement('<div id="' + id + '" hidden="" class="rl-cache-class b-text-part '
.hide() + (isHtml ? 'html' : 'plain') + '">'
.addClass('rl-cache-class'); + findEmailAndLinks(resultHtml)
body.data('rl-cache-count', iMessageBodyCacheCount); + '</div>');
body.rlCacheCount = iMessageBodyCacheCount;
body.html(findEmailAndLinks(resultHtml)).addClass('b-text-part ' + (isHtml ? 'html' : 'plain'));
message.isHtml(!!isHtml); message.isHtml(!!isHtml);
message.hasImages(!!data.Result.HasExternals); message.hasImages(!!data.Result.HasExternals);
message.body = body; messagesDom.append(body);
if (message.body) {
messagesDom.append(message.body);
}
message.storeDataInDom(); message.storeDataInDom();
if (data.Result.HasInternals) { if (data.Result.HasInternals) {
message.showInternalImages(true); message.showInternalImages();
} }
if (message.hasImages() && SettingsStore.showImages()) { if (message.hasImages() && SettingsStore.showImages()) {
message.showExternalImages(true); message.showExternalImages();
} }
this.purgeMessageBodyCacheThrottle(); this.purgeMessageBodyCacheThrottle();
} else {
message.body = textBody;
if (message.body) {
iMessageBodyCacheCount += 1;
message.body.data('rl-cache-count', iMessageBodyCacheCount);
message.fetchDataFromDom();
}
} }
message.body = body || textBody;
this.messageActiveDom(message.body); this.messageActiveDom(message.body);
this.hideMessageBodies(); this.hideMessageBodies();
@ -612,7 +574,7 @@ class MessageUserStore {
this.initBlockquoteSwitcher(body); this.initBlockquoteSwitcher(body);
} }
message.body.show(); message.body.hidden = false;
} }
initMessageFlagsFromCache(message); initMessageFlagsFromCache(message);

View file

@ -1,12 +1,134 @@
import ko from 'ko'; import ko from 'ko';
import { i18n } from 'Common/Translator'; import { i18n } from 'Common/Translator';
import { isNonEmptyArray, pString } from 'Common/Utils'; import { isNonEmptyArray, pString, htmlToElement } from 'Common/Utils';
import AccountStore from 'Stores/User/Account'; import AccountStore from 'Stores/User/Account';
import { showScreenPopup } from 'Knoin/Knoin'; import { showScreenPopup } from 'Knoin/Knoin';
function controlsHelper(dom, verControl, success, title, text)
{
dom.classList.toggle('error', !success);
dom.classList.toggle('success', success);
verControl.classList.toggle('error', !success);
verControl.classList.toggle('success', success);
dom.title = verControl.title = title;
if (undefined !== text) {
dom.textContent = text.trim();
}
}
function domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) {
return function() {
let message = null;
if (this.classList.contains('success')) {
return false;
}
try {
message = store.openpgp.message.readArmored(armoredMessage);
} catch (e) {
console.log(e);
}
if (message && message.getText && message.verify && message.decrypt) {
store.decryptMessage(
message,
recipients,
(validPrivateKey, decryptedMessage, validPublicKey, signingKeyIds) => {
if (decryptedMessage) {
if (validPublicKey) {
controlsHelper(
dom,
this,
true,
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
'USER': validPublicKey.user + ' (' + validPublicKey.id + ')'
}),
decryptedMessage.getText()
);
} else if (validPrivateKey) {
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ')
: '';
controlsHelper(
dom,
this,
false,
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''),
decryptedMessage.getText()
);
} else {
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
}
} else {
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
}
}
);
return false;
}
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
return false;
};
}
function domControlSignedClickHelper(store, dom, armoredMessage) {
return function() {
let message = null;
if (this.classList.contains('success') || this.classList.contains('error')) {
return false;
}
try {
message = store.openpgp.cleartext.readArmored(armoredMessage);
} catch (e) {
console.log(e);
}
if (message && message.getText && message.verify) {
store.verifyMessage(message, (validKey, signingKeyIds) => {
if (validKey) {
controlsHelper(
dom,
this,
true,
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
'USER': validKey.user + ' (' + validKey.id + ')'
}),
message.getText()
);
} else {
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ')
: '';
controlsHelper(
dom,
this,
false,
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
);
}
});
return false;
}
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
return false;
};
}
class PgpUserStore { class PgpUserStore {
constructor() { constructor() {
this.capaOpenPGP = ko.observable(false); this.capaOpenPGP = ko.observable(false);
@ -209,174 +331,34 @@ class PgpUserStore {
return false; return false;
} }
controlsHelper(dom, verControl, success, title, text) {
if (success) {
dom
.removeClass('error')
.addClass('success')
.attr('title', title);
verControl
.removeClass('error')
.addClass('success')
.attr('title', title);
} else {
dom
.removeClass('success')
.addClass('error')
.attr('title', title);
verControl
.removeClass('success')
.addClass('error')
.attr('title', title);
}
if (undefined !== text) {
dom.text(text.trim());
}
}
static domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) {
return function() {
let message = null;
const $this = jQuery(this); // eslint-disable-line no-invalid-this
if ($this.hasClass('success')) {
return false;
}
try {
message = store.openpgp.message.readArmored(armoredMessage);
} catch (e) {
console.log(e);
}
if (message && message.getText && message.verify && message.decrypt) {
store.decryptMessage(
message,
recipients,
(validPrivateKey, decryptedMessage, validPublicKey, signingKeyIds) => {
if (decryptedMessage) {
if (validPublicKey) {
store.controlsHelper(
dom,
$this,
true,
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
'USER': validPublicKey.user + ' (' + validPublicKey.id + ')'
}),
decryptedMessage.getText()
);
} else if (validPrivateKey) {
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ')
: '';
store.controlsHelper(
dom,
$this,
false,
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''),
decryptedMessage.getText()
);
} else {
store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
}
} else {
store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
}
}
);
return false;
}
store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
return false;
};
}
static domControlSignedClickHelper(store, dom, armoredMessage) {
return function() {
let message = null;
const $this = jQuery(this); // eslint-disable-line no-invalid-this
if ($this.hasClass('success') || $this.hasClass('error')) {
return false;
}
try {
message = store.openpgp.cleartext.readArmored(armoredMessage);
} catch (e) {
console.log(e);
}
if (message && message.getText && message.verify) {
store.verifyMessage(message, (validKey, signingKeyIds) => {
if (validKey) {
store.controlsHelper(
dom,
$this,
true,
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
'USER': validKey.user + ' (' + validKey.id + ')'
}),
message.getText()
);
} else {
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ')
: '';
store.controlsHelper(
dom,
$this,
false,
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
);
}
});
return false;
}
store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
return false;
};
}
/** /**
* @param {*} dom * @param {*} dom
* @param {MessageModel} rainLoopMessage * @param {MessageModel} rainLoopMessage
*/ */
initMessageBodyControls(dom, rainLoopMessage) { initMessageBodyControls(dom, rainLoopMessage) {
if (dom && !dom.hasClass('inited')) { const cl = dom && dom.classList;
dom.addClass('inited'); if (!cl.has('inited')) {
cl.add('inited');
const encrypted = dom.hasClass('encrypted'), const encrypted = cl.has('encrypted'),
signed = dom.hasClass('signed'), signed = cl.has('signed'),
recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : []; recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [];
let verControl = null; let verControl = null;
if (encrypted || signed) { if (encrypted || signed) {
const domText = dom.text(); const domText = dom.textContent;
dom.data('openpgp-original', domText);
verControl = htmlToElement('<div class="b-openpgp-control"><i class="icon-lock"></i></div>'); // 🔒
if (encrypted) { if (encrypted) {
verControl = jQuery('<div class="b-openpgp-control"><i class="icon-lock"></i></div>') verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC');
.attr('title', i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC')) verControl.addEventHandler('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
.on('click', PgpUserStore.domControlEncryptedClickHelper(this, dom, domText, recipients)); } else {
} else if (signed) { verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC');
verControl = jQuery('<div class="b-openpgp-control"><i class="icon-lock"></i></div>') verControl.addEventHandler('click', domControlSignedClickHelper(this, dom, domText));
.attr('title', i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC'))
.on('click', PgpUserStore.domControlSignedClickHelper(this, dom, domText));
} }
if (verControl) { dom.before(verControl, document.createElement('div'));
dom.before(verControl).before('<div></div>');
}
} }
} }
} }

View file

@ -2,7 +2,6 @@ import ko from 'ko';
import { MESSAGES_PER_PAGE, MESSAGES_PER_PAGE_VALUES } from 'Common/Consts'; import { MESSAGES_PER_PAGE, MESSAGES_PER_PAGE_VALUES } from 'Common/Consts';
import { Layout, EditorDefaultType } from 'Common/Enums'; import { Layout, EditorDefaultType } from 'Common/Enums';
import { $htmlCL } from 'Common/Globals';
import { pInt } from 'Common/Utils'; import { pInt } from 'Common/Utils';
import * as Settings from 'Storage/Settings'; import * as Settings from 'Storage/Settings';
@ -43,10 +42,11 @@ class SettingsUserStore {
} }
subscribers() { subscribers() {
this.layout.subscribe((value) => { const htmlCL = document.documentElement.classList;
$htmlCL.toggle('rl-no-preview-pane', Layout.NoPreview === value); this.layout.subscribe(value => {
$htmlCL.toggle('rl-side-preview-pane', Layout.SidePreview === value); htmlCL.toggle('rl-no-preview-pane', Layout.NoPreview === value);
$htmlCL.toggle('rl-bottom-preview-pane', Layout.BottomPreview === value); htmlCL.toggle('rl-side-preview-pane', Layout.SidePreview === value);
htmlCL.toggle('rl-bottom-preview-pane', Layout.BottomPreview === value);
dispatchEvent(new CustomEvent('rl-layout', {detail:value})); dispatchEvent(new CustomEvent('rl-layout', {detail:value}));
}); });
} }

View file

@ -376,13 +376,13 @@ html.rl-no-preview-pane {
background-color: #eee; background-color: #eee;
border: 1px solid #999; border: 1px solid #999;
display: inline-block; display: block;
width: 30px; width: 30px;
height: 14px; height: 14px;
line-height: 14px; line-height: 14px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
margin: 10px 0px; margin: 2em 0 10px;
opacity: 0.5; opacity: 0.5;
&:hover { &:hover {

View file

@ -1,7 +1,5 @@
import ko from 'ko'; import ko from 'ko';
import { triggerAutocompleteInputChange } from 'Common/Utils';
import { StorageResultType, Notification } from 'Common/Enums'; import { StorageResultType, Notification } from 'Common/Enums';
import { getNotification } from 'Common/Translator'; import { getNotification } from 'Common/Translator';
@ -37,8 +35,6 @@ class LoginAdminView extends AbstractViewNext {
this.loginErrorAnimation = ko.observable(false).extend({ 'falseTimeout': 500 }); this.loginErrorAnimation = ko.observable(false).extend({ 'falseTimeout': 500 });
this.passwordErrorAnimation = ko.observable(false).extend({ 'falseTimeout': 500 }); this.passwordErrorAnimation = ko.observable(false).extend({ 'falseTimeout': 500 });
// this.loginFocus = ko.observable(false);
this.formHidden = ko.observable(false); this.formHidden = ko.observable(false);
this.formError = ko.computed(() => this.loginErrorAnimation() || this.passwordErrorAnimation()); this.formError = ko.computed(() => this.loginErrorAnimation() || this.passwordErrorAnimation());
@ -59,8 +55,6 @@ class LoginAdminView extends AbstractViewNext {
@command((self) => !self.submitRequest()) @command((self) => !self.submitRequest())
submitCommand() { submitCommand() {
triggerAutocompleteInputChange();
this.loginError(false); this.loginError(false);
this.passwordError(false); this.passwordError(false);
@ -98,13 +92,6 @@ class LoginAdminView extends AbstractViewNext {
routeOff(); routeOff();
} }
onHide() {
}
onBuild() {
triggerAutocompleteInputChange(true);
}
submitForm() { submitForm() {
this.submitCommand(); this.submitCommand();
} }

View file

@ -13,7 +13,6 @@ import {
import { import {
isNonEmptyArray, isNonEmptyArray,
clearBqSwitcher,
replySubjectAdd, replySubjectAdd,
encodeHtml, encodeHtml,
inFocus, inFocus,
@ -855,13 +854,7 @@ class ComposePopupView extends AbstractViewNext {
sDate = momentorFormat(message.dateTimeStampInUTC(), 'FULL'); sDate = momentorFormat(message.dateTimeStampInUTC(), 'FULL');
sSubject = message.subject(); sSubject = message.subject();
aDraftInfo = message.aDraftInfo; aDraftInfo = message.aDraftInfo;
sText = message.bodyAsHTML();
const clonedText = jQuery(message.body).clone();
if (clonedText) {
clearBqSwitcher(clonedText);
sText = clonedText.html();
}
let resplyAllParts = null; let resplyAllParts = null;
switch (lineComposeType) { switch (lineComposeType) {

View file

@ -610,9 +610,7 @@ class ContactsPopupView extends AbstractViewNext {
} }
onBuild(dom) { onBuild(dom) {
this.oContentVisible = jQuery('.b-list-content', dom); this.selector.init(dom[0].querySelector('.b-list-content'), KeyState.ContactList);
this.selector.init(this.oContentVisible, this.oContentVisible, KeyState.ContactList);
key('delete', KeyState.ContactList, () => { key('delete', KeyState.ContactList, () => {
this.deleteCommand(); this.deleteCommand();

View file

@ -120,10 +120,8 @@ class MessageOpenPgpPopupView extends AbstractViewNext {
this.privateKeys(privateKeys); this.privateKeys(privateKeys);
if (this.viewModelDom) { if (this.viewModelDom) {
this.viewModelDom const el = this.viewModelDom.querySelector('.key-list__item');
.find('.key-list__item') el && el.click();
.first()
.click();
} }
} }
} }

View file

@ -8,7 +8,7 @@ import {
Notification Notification
} from 'Common/Enums'; } from 'Common/Enums';
import { convertLangName, triggerAutocompleteInputChange } from 'Common/Utils'; import { convertLangName } from 'Common/Utils';
import { getNotification, getNotificationFromResponse, reload as translatorReload } from 'Common/Translator'; import { getNotification, getNotificationFromResponse, reload as translatorReload } from 'Common/Translator';
@ -72,9 +72,6 @@ class LoginUserView extends AbstractViewNext {
(this.additionalCode.visibility() && this.additionalCode.errorAnimation()) (this.additionalCode.visibility() && this.additionalCode.errorAnimation())
); );
// this.emailFocus = ko.observable(false);
// this.passwordFocus = ko.observable(false);
this.email.subscribe(() => { this.email.subscribe(() => {
this.emailError(false); this.emailError(false);
this.additionalCode(''); this.additionalCode('');
@ -144,8 +141,6 @@ class LoginUserView extends AbstractViewNext {
@command((self) => !self.submitRequest()) @command((self) => !self.submitRequest())
submitCommand() { submitCommand() {
triggerAutocompleteInputChange();
this.emailError(false); this.emailError(false);
this.passwordError(false); this.passwordError(false);
@ -280,8 +275,6 @@ class LoginUserView extends AbstractViewNext {
); );
}); });
}, 50); }, 50);
triggerAutocompleteInputChange(true);
} }
submitForm() { submitForm() {
@ -291,16 +284,6 @@ class LoginUserView extends AbstractViewNext {
selectLanguage() { selectLanguage() {
showScreenPopup(require('View/Popup/Languages'), [this.language, this.languages(), LanguageStore.userLanguage()]); showScreenPopup(require('View/Popup/Languages'), [this.language, this.languages(), LanguageStore.userLanguage()]);
} }
selectLanguageOnTab(bShift) {
if (!bShift) {
// setTimeout(() => this.emailFocus(true), 50);
return false;
}
return true;
}
} }
export { LoginUserView, LoginUserView as default }; export { LoginUserView, LoginUserView as default };

View file

@ -17,8 +17,6 @@ import { getApp } from 'Helper/Apps/User';
import { view, ViewType, showScreenPopup, setHash } from 'Knoin/Knoin'; import { view, ViewType, showScreenPopup, setHash } from 'Knoin/Knoin';
import { AbstractViewNext } from 'Knoin/AbstractViewNext'; import { AbstractViewNext } from 'Knoin/AbstractViewNext';
const $ = jQuery;
@view({ @view({
name: 'View/User/MailBox/FolderList', name: 'View/User/MailBox/FolderList',
type: ViewType.Left, type: ViewType.Left,
@ -28,7 +26,6 @@ class FolderListMailBoxUserView extends AbstractViewNext {
constructor() { constructor() {
super(); super();
this.oContentVisible = null;
this.oContentScrollable = null; this.oContentScrollable = null;
this.composeInEdit = AppStore.composeInEdit; this.composeInEdit = AppStore.composeInEdit;
@ -61,10 +58,8 @@ class FolderListMailBoxUserView extends AbstractViewNext {
} }
onBuild(dom) { onBuild(dom) {
this.oContentVisible = $('.b-content', dom);
this.oContentScrollable = this.oContentVisible ? this.oContentVisible[0] : null;
const self = this, const self = this,
qs = s => dom[0].querySelector(s),
isMobile = Settings.appSettingsGet('mobile'), isMobile = Settings.appSettingsGet('mobile'),
fSelectFolder = (el, event, starred) => { fSelectFolder = (el, event, starred) => {
const isMove = moveAction(); const isMove = moveAction();
@ -108,6 +103,8 @@ class FolderListMailBoxUserView extends AbstractViewNext {
} }
}; };
this.oContentScrollable = qs('.b-content');
dom dom
.on('click', '.b-folders .e-item .e-link .e-collapsed-sign', function(event) { .on('click', '.b-folders .e-item .e-link .e-collapsed-sign', function(event) {
// eslint-disable-line prefer-arrow-callback // eslint-disable-line prefer-arrow-callback
@ -132,21 +129,22 @@ class FolderListMailBoxUserView extends AbstractViewNext {
key('up, down', KeyState.FolderList, (event, handler) => { key('up, down', KeyState.FolderList, (event, handler) => {
const keyCode = handler && 'up' === handler.shortcut ? EventKeyCode.Up : EventKeyCode.Down, const keyCode = handler && 'up' === handler.shortcut ? EventKeyCode.Up : EventKeyCode.Down,
$items = $('.b-folders .e-item .e-link:not(.hidden):visible', dom); $items = jQuery('.b-folders .e-item .e-link:not(.hidden):visible', dom);
let index = $items.length;
if (event && $items.length) { if (event && index) {
let index = $items.index($items.filter('.focused')); while (index--) {
if (-1 < index) { if ($items[index].matches('.focused')) {
$items.eq(index).removeClass('focused'); $items[index].classList.remove('focused');
break;
}
} }
if (EventKeyCode.Up === keyCode && 0 < index) { if (EventKeyCode.Up === keyCode && 0 < index) {
index -= 1; --index;
} else if (EventKeyCode.Down === keyCode && index < $items.length - 1) { } else if (EventKeyCode.Down === keyCode && index < $items.length - 1) {
index += 1; ++index;
} }
$items.eq(index).addClass('focused'); $items[index].classList.add('focused');
self.scrollToFocused(); self.scrollToFocused();
} }
@ -154,25 +152,23 @@ class FolderListMailBoxUserView extends AbstractViewNext {
}); });
key('enter', KeyState.FolderList, () => { key('enter', KeyState.FolderList, () => {
const $items = $('.b-folders .e-item .e-link:not(.hidden).focused', dom); const item = qs('.b-folders .e-item .e-link:not(.hidden).focused');
if ($items.length && $items[0]) { if (item) {
AppStore.focusedState(Focused.MessageList); AppStore.focusedState(Focused.MessageList);
$items.click(); item.click();
} }
return false; return false;
}); });
key('space', KeyState.FolderList, () => { key('space', KeyState.FolderList, () => {
const $items = $('.b-folders .e-item .e-link:not(.hidden).focused', dom); const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'),
if ($items.length && $items[0]) { folder = item && ko.dataFor(item);
const folder = ko.dataFor($items[0]);
if (folder) { if (folder) {
const collapsed = folder.collapsed(); const collapsed = folder.collapsed();
getApp().setExpandedFolder(folder.fullNameHash, collapsed); getApp().setExpandedFolder(folder.fullNameHash, collapsed);
folder.collapsed(!collapsed); folder.collapsed(!collapsed);
} }
}
return false; return false;
}); });
@ -183,10 +179,12 @@ class FolderListMailBoxUserView extends AbstractViewNext {
return false; return false;
}); });
AppStore.focusedState.subscribe((value) => { AppStore.focusedState.subscribe(value => {
$('.b-folders .e-item .e-link.focused', dom).removeClass('focused'); let el = qs('.b-folders .e-item .e-link.focused');
el && qs('.b-folders .e-item .e-link.focused').classList.remove('focused');
if (Focused.FolderList === value) { if (Focused.FolderList === value) {
$('.b-folders .e-item .e-link.selected', dom).addClass('focused'); el = qs('.b-folders .e-item .e-link.selected');
el && qs('.b-folders .e-item .e-link.selected').classList.add('focused');
} }
}); });
} }
@ -206,28 +204,20 @@ class FolderListMailBoxUserView extends AbstractViewNext {
} }
scrollToFocused() { scrollToFocused() {
if (!this.oContentVisible || !this.oContentScrollable) { const scrollable = this.oContentScrollable;
return false; if (scrollable) {
let block, focused = scrollable.querySelector('.e-item .e-link.focused');
if (focused) {
const fRect = focused.getBoundingClientRect(),
sRect = scrollable.getBoundingClientRect();
if (fRect.top < sRect.top) {
block = 'start';
} else if (fRect.bottom > sRect.bottom) {
block = 'end';
} }
block && focused.scrollIntoView(block === 'start');
const offset = 20,
focused = $('.e-item .e-link.focused', this.oContentScrollable),
pos = focused.position(),
visibleHeight = this.oContentVisible.height(),
focusedHeight = focused.outerHeight();
if (pos && (0 > pos.top || pos.top + focusedHeight > visibleHeight)) {
let top = this.oContentScrollable.scrollTop + pos.top;
if (0 > pos.top) {
this.oContentScrollable.scrollTop = top - offset;
} else {
this.oContentScrollable.scrollTop = top - visibleHeight + focusedHeight + offset;
} }
return true;
} }
return false;
} }
/** /**
@ -237,9 +227,9 @@ class FolderListMailBoxUserView extends AbstractViewNext {
*/ */
messagesDrop(toFolder, ui) { messagesDrop(toFolder, ui) {
if (toFolder && ui && ui.helper) { if (toFolder && ui && ui.helper) {
const fromFolderFullNameRaw = ui.helper.data('rl-folder'), const fromFolderFullNameRaw = ui.helper.rlFolder,
copy = $htmlCL.contains('rl-ctrl-key-pressed'), copy = $htmlCL.contains('rl-ctrl-key-pressed'),
uids = ui.helper.data('rl-uids'); uids = ui.helper.rlUids;
if (fromFolderFullNameRaw && Array.isArray(uids)) { if (fromFolderFullNameRaw && Array.isArray(uids)) {
getApp().moveMessagesToFolder(fromFolderFullNameRaw, uids, toFolder.fullNameRaw, copy); getApp().moveMessagesToFolder(fromFolderFullNameRaw, uids, toFolder.fullNameRaw, copy);

View file

@ -15,7 +15,7 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { bMobileDevice, leftPanelDisabled, moveAction } from 'Common/Globals'; import { bMobileDevice, leftPanelDisabled, moveAction } from 'Common/Globals';
import { computedPagenatorHelper, draggablePlace, friendlySize } from 'Common/Utils'; import { computedPagenatorHelper, friendlySize, htmlToElement } from 'Common/Utils';
import { mailBox, append } from 'Common/Links'; import { mailBox, append } from 'Common/Links';
import { Selector } from 'Common/Selector'; import { Selector } from 'Common/Selector';
@ -267,9 +267,9 @@ class MessageListMailBoxUserView extends AbstractViewNext {
} }
}); });
MessageStore.messageListEndHash.subscribe(() => { MessageStore.messageListEndHash.subscribe((() =>
this.selector.scrollToTop(); this.selector.scrollToFocused()
}); ).throttle(50));
} }
@command() @command()
@ -502,14 +502,20 @@ class MessageListMailBoxUserView extends AbstractViewNext {
oMessageListItem.checked(true); oMessageListItem.checked(true);
} }
const el = draggablePlace(), const el = htmlToElement('<div class="draggablePlace">' +
'<span class="text"></span>&nbsp;' +
'<i class="icon-copy icon-white visible-on-ctrl"></i>' +
'<i class="icon-mail icon-white hidden-on-ctrl"></i>' +
'</div>'),
updateUidsInfo = () => { updateUidsInfo = () => {
const uids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails(); const uids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails();
el.data('rl-uids', uids); el.rlUids = uids;
el.find('.text').text('' + uids.length); el.querySelector('.text').textContent = uids.length;
}; };
el.data('rl-folder', FolderStore.currentFolderFullNameRaw()); document.getElementById('rl-hidden').append(el);
el.rlFolder = FolderStore.currentFolderFullNameRaw();
updateUidsInfo(); updateUidsInfo();
setTimeout(updateUidsInfo,1); setTimeout(updateUidsInfo,1);
@ -735,9 +741,7 @@ class MessageListMailBoxUserView extends AbstractViewNext {
onBuild(dom) { onBuild(dom) {
const self = this; const self = this;
this.oContentVisible = jQuery('.b-content', dom); this.selector.init(dom[0].querySelector('.b-content'), KeyState.MessageList);
this.selector.init(this.oContentVisible, this.oContentVisible, KeyState.MessageList);
if (this.mobile) { if (this.mobile) {
dom.on('click', () => { dom.on('click', () => {

View file

@ -243,8 +243,8 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
return ''; return '';
}); });
this.messageActiveDom.subscribe((dom) => { this.messageActiveDom.subscribe(dom => {
this.bodyBackgroundColor(dom ? this.detectDomBackgroundColor(dom) : ''); this.bodyBackgroundColor(this.detectDomBackgroundColor(dom));
}, this); }, this);
this.message.subscribe((message) => { this.message.subscribe((message) => {
@ -357,45 +357,26 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
} }
detectDomBackgroundColor(dom) { detectDomBackgroundColor(dom) {
let limit = 5,
result = '';
const fFindDom = function(inputDom) {
const children = inputDom ? inputDom.children() : null;
return children && 1 === children.length && children.is('table,div,center') ? children : null;
},
fFindColor = function(inputDom) {
let color = ''; let color = '';
if (inputDom) {
color = inputDom.css('background-color') || ''; if (dom) {
if (!inputDom.is('table')) { let limit = 5,
aC = dom;
while (!color && aC && limit--) {
let children = aC.children;
if (!children || 1 !== children.length || !children[0].matches('table,div,center')) break;
aC = children[0];
color = aC.style.backgroundColor || '';
if (!aC.matches('table')) {
color = isTransparent(color) ? '' : color; color = isTransparent(color) ? '' : color;
} }
} }
color = isTransparent(color) ? '' : color;
}
return color; return color;
};
if (dom && 1 === dom.length) {
let aC = dom;
while (!result) {
limit -= 1;
if (0 >= limit) {
break;
}
aC = fFindDom(aC);
if (aC) {
result = fFindColor(aC);
} else {
break;
}
}
result = isTransparent(result) ? '' : result;
}
return result;
} }
fullScreen() { fullScreen() {
@ -545,7 +526,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
!!event && !!event &&
3 !== event.which && 3 !== event.which &&
mailToHelper( mailToHelper(
jQuery(this).attr('href'), this.href,
Settings.capa(Capa.Composer) ? require('View/Popup/Compose') : null // eslint-disable-line no-invalid-this Settings.capa(Capa.Composer) ? require('View/Popup/Compose') : null // eslint-disable-line no-invalid-this
) )
); );
@ -701,13 +682,11 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
// toggle message blockquotes // toggle message blockquotes
key('b', [KeyState.MessageList, KeyState.MessageView], () => { key('b', [KeyState.MessageList, KeyState.MessageView], () => {
if (MessageStore.message() && MessageStore.message().body) { const message = MessageStore.message();
MessageStore.message() if (message && message.body) {
.body.find('.rlBlockquoteSwitcher') message.body.querySelectorAll('.rlBlockquoteSwitcher').forEach(node => node.click());
.click();
return false; return false;
} }
return true; return true;
}); });
@ -878,7 +857,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
*/ */
showImages(message) { showImages(message) {
if (message && message.showExternalImages) { if (message && message.showExternalImages) {
message.showExternalImages(true); message.showExternalImages();
} }
} }

View file

@ -16,7 +16,7 @@ progress.className = "progressjs-inner";
progress.appendChild(doc.createElement('div')).className = "progressjs-percent"; progress.appendChild(doc.createElement('div')).className = "progressjs-percent";
setPercentWidth(1); setPercentWidth(1);
doc.body.appendChild(container); doc.body.append(container);
win.progressJs = new class { win.progressJs = new class {
set(percent) { set(percent) {
@ -80,7 +80,7 @@ function writeCSS(css) {
const style = doc.createElement('style'); const style = doc.createElement('style');
style.type = 'text/css'; style.type = 'text/css';
style.textContent = css; style.textContent = css;
doc.head.appendChild(style); doc.head.append(style);
} }
function loadScript(src) { function loadScript(src) {
@ -93,8 +93,8 @@ function loadScript(src) {
script.onerror = () => reject(new Error(src)); script.onerror = () => reject(new Error(src));
script.src = src; script.src = src;
// script.type = 'text/javascript'; // script.type = 'text/javascript';
doc.head.appendChild(script); doc.head.append(script);
// doc.body.appendChild(element); // doc.body.append(element);
}); });
} }

View file

@ -9,7 +9,7 @@
<form class="wrapper submitting-pane loginForm thm-login thm-login-text" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError, 'submitting': submitRequest()}"> <form class="wrapper submitting-pane loginForm thm-login thm-login-text" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError, 'submitting': submitRequest()}">
<div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}"> <div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="text" class="input-block-level inputLogin checkAutocomplete" <input type="text" class="input-block-level inputLogin"
name="RainLoopAdminLogin" id="RainLoopAdminLogin" name="RainLoopAdminLogin" id="RainLoopAdminLogin"
autofocus="" autofocus=""
autocomplete="username" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_LOGIN" autocomplete="username" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_LOGIN"
@ -21,7 +21,7 @@
</div> </div>
<div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}"> <div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="password" class="input-block-level inputPassword checkAutocomplete" <input type="password" class="input-block-level inputPassword"
placeholder="Password" name="RainLoopAdminPassword" id="RainLoopAdminPassword" placeholder="Password" name="RainLoopAdminPassword" id="RainLoopAdminPassword"
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD"
data-bind="textInput: password, disable: submitRequest" /> data-bind="textInput: password, disable: submitRequest" />

View file

@ -1,14 +0,0 @@
<input class="i18n" type="date" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="attr: {'placeholder': placeholder}, enable: enable, css: className" />
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
&nbsp;
<!-- /ko -->
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger }
}"></div>
<!-- /ko -->

View file

@ -1,7 +0,0 @@
<div class="e-component e-radio" data-bind="foreach: values">
<label tabindex="0" data-bind="click: $parent.click, css: {'inline': $parent.inline}">
<i role="radio" data-bind="css: $parent.value() === value ? 'icon-radio-checked' : 'icon-radio-unchecked'"></i>
&nbsp;&nbsp;
<span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span>
</label>
</div>

View file

@ -31,7 +31,7 @@
<div class="controls plugin-mark-Login-TopControlGroup" <div class="controls plugin-mark-Login-TopControlGroup"
data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}"> data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="email" class="i18n input-block-level inputEmail checkAutocomplete" <input type="email" class="i18n input-block-level inputEmail"
name="RainLoopEmail" id="RainLoopEmail" name="RainLoopEmail" id="RainLoopEmail"
autofocus="" autofocus=""
autocomplete="email" autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="email" autocorrect="off" autocapitalize="off" spellcheck="false"
@ -44,7 +44,7 @@
</div> </div>
<div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}"> <div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="password" class="i18n input-block-level inputPassword checkAutocomplete" <input type="password" class="i18n input-block-level inputPassword"
name="RainLoopPassword" id="RainLoopPassword" name="RainLoopPassword" id="RainLoopPassword"
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: password, disable: submitRequest" data-bind="textInput: password, disable: submitRequest"

View file

@ -6,8 +6,8 @@
* *
* @requires: * @requires:
* *
* jQuery 1.7+ * jQuery 3.5+
* jQueryUI 1.8+ Core * jQueryUI 1.12+ Core
* *
* @version 0.1.6 * @version 0.1.6
* @author Dan Kielp <dan@sproutsocial.com> * @author Dan Kielp <dan@sproutsocial.com>