From bdb36ec128f19901b60f7e7c0bf763a8278cd5f5 Mon Sep 17 00:00:00 2001 From: djmaze Date: Thu, 27 Aug 2020 15:45:47 +0200 Subject: [PATCH] Use less jQuery, more native --- README.md | 24 +- dev/App/Abstract.js | 6 +- dev/App/User.js | 4 +- dev/Common/Booter.js | 8 +- dev/Common/Globals.js | 4 +- dev/Common/HtmlEditor.js | 3 +- dev/Common/Selector.js | 63 ++-- dev/Common/Translator.js | 49 ++- dev/Common/Utils.js | 140 +-------- dev/Component/Abstract.js | 2 +- dev/External/ko.js | 19 +- dev/Knoin/AbstractViewNext.js | 2 +- dev/Knoin/Knoin.js | 42 ++- dev/Model/Message.js | 157 +++++----- dev/Remote/AbstractAjax.js | 1 - dev/Screen/AbstractSettings.js | 26 +- dev/Screen/User/MailBox.js | 2 +- dev/Stores/User/Message.js | 142 ++++----- dev/Stores/User/Pgp.js | 290 ++++++++---------- dev/Stores/User/Settings.js | 10 +- dev/Styles/MessageView.less | 4 +- dev/View/Admin/Login.js | 13 - dev/View/Popup/Compose.js | 9 +- dev/View/Popup/Contacts.js | 4 +- dev/View/Popup/MessageOpenPgp.js | 6 +- dev/View/User/Login.js | 19 +- dev/View/User/MailBox/FolderList.js | 94 +++--- dev/View/User/MailBox/MessageList.js | 26 +- dev/View/User/MailBox/MessageView.js | 61 ++-- dev/boot.js | 8 +- .../app/templates/Views/Admin/AdminLogin.html | 4 +- .../app/templates/Views/Components/Date.html | 14 - .../app/templates/Views/Components/Radio.html | 7 - .../0.0.0/app/templates/Views/User/Login.html | 4 +- vendors/inputosaurus/inputosaurus.js | 4 +- 35 files changed, 492 insertions(+), 779 deletions(-) delete mode 100644 rainloop/v/0.0.0/app/templates/Views/Components/Date.html delete mode 100644 rainloop/v/0.0.0/app/templates/Views/Components/Radio.html diff --git a/README.md b/README.md index e41b730ed..b3bf97bc9 100644 --- a/README.md +++ b/README.md @@ -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 | |----------- |--------: |--------: | -|admin.js |2.130.942 |1.007.370 | -|app.js |4.184.455 |2.676.306 | -|boot.js | 671.522 | 43.856 | -|libs.js | 647.614 | 316.969 | +|admin.js |2.130.942 | 973.239 | +|app.js |4.184.455 |2.640.205 | +|boot.js | 671.522 | 43.824 | +|libs.js | 647.614 | 316.970 | |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 | |--------------- |--------: |--------: |--------: |--------: |--------: | -|admin.min.js | 252.147 | 138.101 | 73.657 | 40.104 | 34.210 | -|app.min.js | 511.202 | 360.198 |140.462 | 95.043 | 76.240 | -|boot.min.js | 66.007 | 5.575 | 22.567 | 2.340 | 2.000 | +|admin.min.js | 252.147 | 132.156 | 73.657 | 38.184 | 32.740 | +|app.min.js | 511.202 | 356.109 |140.462 | 93.738 | 75.150 | +|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 | |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 | |-------------- |--------: |--------: | -|app.css | 340.334 | 266.769 | -|app.min.css | 274.791 | 211.601 | +|app.css | 340.334 | 266.739 | +|app.min.css | 274.791 | 211.574 | ### PHP73 branch diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index 512e44f76..da744495f 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -72,7 +72,7 @@ class AbstractApp extends AbstractBoot { } else { const oLink = document.createElement('a'); oLink.href = link; - document.body.appendChild(oLink).click(); + document.body.append(oLink).click(); oLink.remove(); // open(link, '_self'); } @@ -165,11 +165,7 @@ class AbstractApp extends AbstractBoot { ko.components.register('SaveTrigger', require('Component/SaveTrigger').default); ko.components.register('Input', require('Component/Input').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('Date', require('Component/Date').default); - - ko.components.register('x-script', require('Component/Script').default); if (Settings.appSettingsGet('materialDesign') && !bMobileDevice) { ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox').default); diff --git a/dev/App/User.js b/dev/App/User.js index 3c7b277c2..d3d21eaac 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -819,7 +819,7 @@ class AppUser extends AbstractApp { const resizer = doc.createElement('div'), cssint = s => parseFloat(getComputedStyle(source, null).getPropertyValue(s).replace('px', '')); resizer.className = 'resizer'; - source.appendChild(resizer); + source.append(resizer); resizer.addEventListener('mousedown', { source: source, mode: mode, @@ -995,7 +995,7 @@ class AppUser extends AbstractApp { script.onload = openpgpCallback; script.onerror = () => console.error(script.src); script.src = openPgpJs(); - doc.head.appendChild(script); + doc.head.append(script); } } else { PgpStore.capaOpenPGP(false); diff --git a/dev/Common/Booter.js b/dev/Common/Booter.js index 6fe02aa74..e8f0b1196 100644 --- a/dev/Common/Booter.js +++ b/dev/Common/Booter.js @@ -45,8 +45,8 @@ function writeCSS(css) { const style = doc.createElement('style'); style.type = 'text/css'; style.textContent = css; -// style.appendChild(doc.createTextNode(styles)); - doc.head.appendChild(style); +// style.append(doc.createTextNode(styles)); + doc.head.append(style); } function loadScript(src) { @@ -59,8 +59,8 @@ function loadScript(src) { script.onerror = () => reject(new Error(src)); script.src = src; // script.type = 'text/javascript'; - doc.head.appendChild(script); -// doc.body.appendChild(element); + doc.head.append(script); +// doc.body.append(element); }); } diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index bd5c07dc4..4b68fab72 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -1,8 +1,8 @@ import ko from 'ko'; import { KeyState } from 'Common/Enums'; -export const $html = jQuery('html'); -export const $htmlCL = document.documentElement.classList; +export const $html = document.documentElement; +export const $htmlCL = $html.classList; /** * @type {?} diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js index fcfc4ef0b..49ce273e6 100644 --- a/dev/Common/HtmlEditor.js +++ b/dev/Common/HtmlEditor.js @@ -106,7 +106,6 @@ class HtmlEditor { this.onModeChange = onModeChange; this.element = element; - this.$element = jQuery(element); this.resize = this.resizeEditor.throttle(100); @@ -414,7 +413,7 @@ class HtmlEditor { resizeEditor() { if (this.editor && this.__resizable) { 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 } } diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 1e7139d16..45a00ac6c 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -15,7 +15,6 @@ class Selector { iSelectNextHelper = 0; iFocusedNextHelper = 0; - oContentVisible; oContentScrollable; sItemSelector; @@ -262,12 +261,11 @@ class Selector { this.focusedItem(null); } - init(contentVisible, contentScrollable, keyScope = 'all') { - this.oContentVisible = contentVisible; - this.oContentScrollable = contentScrollable ? contentScrollable[0] : null; + init(contentScrollable, keyScope = 'all') { + this.oContentScrollable = contentScrollable; - if (this.oContentVisible && this.oContentScrollable) { - jQuery(this.oContentVisible) + if (contentScrollable) { + jQuery(contentScrollable) .on('selectstart', (event) => { if (event && event.preventDefault) { event.preventDefault(); @@ -289,8 +287,9 @@ class Selector { }); key('enter', keyScope, () => { - if (this.focusedItem() && !this.focusedItem().selected()) { - this.actionClick(this.focusedItem()); + const focused = this.focusedItem(); + if (focused && !focused.selected()) { + this.actionClick(focused); return false; } @@ -500,45 +499,29 @@ class Selector { * @returns {boolean} */ scrollToFocused() { - if (!this.oContentVisible || !this.oContentScrollable) { - return false; + const scrollable = this.oContentScrollable; + 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} */ scrollToTop() { - if (!this.oContentVisible || !this.oContentScrollable) { - return false; - } - - this.oContentScrollable.scrollTop = 0; - - return true; + this.oContentScrollable && (this.oContentScrollable.scrollTop = 0); } eventClickFunction(item, event) { diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js index 6c40f77ac..893c023fa 100644 --- a/dev/Common/Translator.js +++ b/dev/Common/Translator.js @@ -1,13 +1,12 @@ import ko from 'ko'; import { Notification, UploadErrorCode } from 'Common/Enums'; -import { pInt } from 'Common/Utils'; -import { $html, $htmlCL } from 'Common/Globals'; import { langLink } from 'Common/Links'; let I18N_DATA = window.rainloopI18N || {}; -const I18N_NOTIFICATION_DATA = {}; -const I18N_NOTIFICATION_MAP = [ +const doc = document, +I18N_NOTIFICATION_DATA = {}, +I18N_NOTIFICATION_MAP = [ [Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'], [Notification.InvalidToken, 'NOTIFICATIONS/INVALID_TOKEN'], [Notification.AuthError, 'NOTIFICATIONS/AUTH_ERROR'], @@ -101,26 +100,24 @@ export function i18n(key, valueList, defaulValue) { return result; } -const i18nToNode = (element) => { - const $el = jQuery(element), - key = $el.data('i18n'); - +const i18nToNode = element => { + const key = element.dataset.i18n; if (key) { if ('[' === key.substr(0, 1)) { switch (key.substr(0, 6)) { case '[html]': - $el.html(i18n(key.substr(6))); + element.innerHTML = i18n(key.substr(6)); break; case '[place': - $el.attr('placeholder', i18n(key.substr(13))); + element.placeholder = i18n(key.substr(13)); break; case '[title': - $el.attr('title', i18n(key.substr(7))); + element.title = i18n(key.substr(7)); break; // no default } } else { - $el.text(i18n(key)); + element.textContent = i18n(key); } } }; @@ -129,11 +126,9 @@ const i18nToNode = (element) => { * @param {Object} elements * @param {boolean=} animate = false */ -export function i18nToNodes(elements) { +export function i18nToNodes(element) { setTimeout(() => - jQuery('[data-i18n]', elements).each((index, item) => { - i18nToNode(item); - }) + element.querySelectorAll('[data-i18n]').forEach(item => i18nToNode(item)) , 1); } @@ -141,7 +136,7 @@ const reloadData = () => { if (window.rainloopI18N) { I18N_DATA = window.rainloopI18N || {}; - i18nToNodes(document); + i18nToNodes(doc); dispatchEvent(new CustomEvent('reload-time')); trigger(!trigger()); @@ -209,7 +204,7 @@ export function getNotification(code, message = '', defCode = null) { */ export function getNotificationFromResponse(response, defCode = Notification.UnknownNotification) { return response && response.ErrorCode - ? getNotification(pInt(response.ErrorCode), response.ErrorMessage || '') + ? getNotification(parseInt(response.ErrorCode, 10) || defCode, response.ErrorMessage || '') : getNotification(defCode); } @@ -253,8 +248,6 @@ export function getUploadErrorDescByCode(code) { export function reload(admin, language) { const start = Date.now(); - $htmlCL.add('rl-changing-language'); - return new Promise((resolve, reject) => { return fetch(langLink(language, admin), {cache: 'reload'}) .then(response => { @@ -269,18 +262,19 @@ export function reload(admin, language) { reject(new Error(error.message)) }) .then(data => { - var script = document.createElement('script'); + var script = doc.createElement('script'); script.text = data; - document.head.appendChild(script).parentNode.removeChild(script); + doc.head.append(script).remove(); setTimeout( () => { 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'); - // $html.attr('dir', isRtl ? 'rtl' : 'ltr') - $htmlCL.add(isRtl ? 'rl-rtl' : 'rl-ltr'); + htmlCL.remove('rl-rtl', 'rl-ltr'); + htmlCL.add(isRtl ? 'rl-rtl' : 'rl-ltr'); + // doc.documentElement.dir = isRtl ? 'rtl' : 'ltr' resolve(); }, @@ -289,6 +283,3 @@ export function reload(admin, language) { }); }); } - -// init section -$htmlCL.add('rl-' + ($html.attr('dir') || 'ltr')); diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index b1f893270..b5ad9c9a3 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -3,8 +3,7 @@ import { Mime } from 'Common/Mime'; const doc = document, - $ = jQuery, - $div = $('
'), + tpl = doc.createElement('template'), isArray = Array.isArray, htmlmap = { '&': '&', @@ -15,6 +14,13 @@ const }, 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 {boolean=} includeZero = true @@ -137,7 +143,7 @@ export function inFocus() { try { if (doc.activeElement) { if (undefined === doc.activeElement.__inFocusCache) { - doc.activeElement.__inFocusCache = $(doc.activeElement).is( + doc.activeElement.__inFocusCache = doc.activeElement.matches( 'input,textarea,iframe,.cke_editable' ); } @@ -156,8 +162,7 @@ export function inFocus() { export function removeInFocus(force) { if (doc.activeElement && doc.activeElement.blur) { try { - const activeEl = $(doc.activeElement); - if (force || (activeEl && activeEl.is('input,textarea'))) { + if (force || doc.activeElement.matches('input,textarea')) { doc.activeElement.blur(); } } catch (e) {} // eslint-disable-line no-empty @@ -275,19 +280,6 @@ export function convertLangName(language, isEng = false) { ); } -/** - * @returns {object} - */ -export function draggablePlace() { - return $( - '
' + - ' ' + - '' + - '' + - '
' - ).appendTo('#rl-hidden'); -} - /** * @param {object} domOption * @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 {?} koTrigger @@ -483,7 +416,7 @@ export function htmlToPlain(html) { fixAttibuteValue = (...args) => (args && 1 < args.length ? '' + args[1] + htmlspecialchars(args[2]) : ''), convertLinks = (...args) => (args && 1 < args.length ? args[1].trim() : ''); - text = html + tpl.innerHTML = html .replace(/]*><\/p>/gi, '') .replace(/]*>([\s\S\r\n\t]*)<\/pre>/gim, convertPre) .replace(/[\s]+/gm, ' ') @@ -507,16 +440,13 @@ export function htmlToPlain(html) { .replace(/"/gi, '"') .replace(/<[^>]*>/gm, ''); - text = $div.html(text).text(); - - text = text + text = splitPlainText(tpl.textContent .replace(/\n[ \t]+/gm, '\n') .replace(/[\n]{3,}/gm, '\n\n') .replace(/>/gi, '>') .replace(/</gi, '<') - .replace(/&/gi, '&'); - - text = splitPlainText(text); + .replace(/&/gi, '&') + ); pos = 0; limit = 800; @@ -530,7 +460,6 @@ export function htmlToPlain(html) { if ((-1 === iP2 || iP3 < iP2) && iP1 < iP3) { text = text.substring(0, iP1) + convertBlockquote(text.substring(iP1 + 13, iP3)) + text.substring(iP3 + 11); - pos = 0; } else if (-1 < iP2 && iP2 < iP3) { pos = iP2 - 1; @@ -552,7 +481,7 @@ export function htmlToPlain(html) { * @param {boolean} findEmailAndLinksInText = false * @returns {string} */ -export function plainToHtml(plain, findEmailAndLinksInText = false) { +export function plainToHtml(plain) { plain = plain.toString().replace(/\r/g, ''); plain = plain.replace(/^>[> ]>+/gm, ([match]) => (match ? match.replace(/[ ]+/g, '') : match)); @@ -595,9 +524,7 @@ export function plainToHtml(plain, findEmailAndLinksInText = false) { aText = aNextText; } while (bDo); - plain = aText.join('\n'); - - plain = plain + return aText.join('\n') // .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n') .replace(/&/g, '&') .replace(/>/g, '>') @@ -605,8 +532,6 @@ export function plainToHtml(plain, findEmailAndLinksInText = false) { .replace(/~~~blockquote~~~[\s]*/g, '
') .replace(/[\s]*~~~\/blockquote~~~/g, '
') .replace(/\n/g, '
'); - - return findEmailAndLinksInText ? findEmailAndLinks(plain) : plain; } window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation @@ -766,39 +691,6 @@ export function selectElement(element) { 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 * @returns {void} diff --git a/dev/Component/Abstract.js b/dev/Component/Abstract.js index 8fe531ed1..03fb22e97 100644 --- a/dev/Component/Abstract.js +++ b/dev/Component/Abstract.js @@ -30,7 +30,7 @@ const componentExportHelper = (ClassObject, templateID = '') => ({ params.component = componentInfo; params.element = jQuery(componentInfo.element); - i18nToNodes(params.element); + i18nToNodes(componentInfo.element); if (undefined !== params.inline && ko.unwrap(params.inline)) { params.element.css('display', 'inline-block'); diff --git a/dev/External/ko.js b/dev/External/ko.js index bed90edc1..656229039 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -2,6 +2,7 @@ import { SaveSettingsStep } from 'Common/Enums'; const $ = jQuery, + doc = document, ko = window.ko, Translator = () => require('Common/Translator'), isFunction = v => typeof v === 'function'; @@ -75,7 +76,7 @@ ko.bindingHandlers.tooltip = { ko.bindingHandlers.tooltipErrorTip = { 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')); }, update: (element, fValueAccessor) => { @@ -197,13 +198,13 @@ ko.bindingHandlers.modal = { }); }, update: (element, fValueAccessor) => { - const Globals = require('Common/Globals'); + const htmlCL = doc.documentElement.classList; $(element).modal(ko.unwrap(fValueAccessor()) ? 'show' : 'hide'); - if (Globals.$htmlCL.contains('no-mobile')) { - Globals.$htmlCL.add('rl-modal-animation'); - setTimeout(() => Globals.$htmlCL.remove('rl-modal-animation'), 500); + if (htmlCL.contains('no-mobile')) { + htmlCL.add('rl-modal-animation'); + setTimeout(() => htmlCL.remove('rl-modal-animation'), 500); } } }; @@ -226,11 +227,11 @@ ko.bindingHandlers.i18nUpdate = { }; ko.bindingHandlers.link = { - update: (element, fValueAccessor) => element.setAttribute('href', ko.unwrap(fValueAccessor())) + update: (element, fValueAccessor) => element.href = ko.unwrap(fValueAccessor()) }; ko.bindingHandlers.title = { - update: (element, fValueAccessor) => element.setAttribute('title', ko.unwrap(fValueAccessor())) + update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor()) }; ko.bindingHandlers.initDom = { @@ -247,7 +248,7 @@ ko.bindingHandlers.draggable = { scrollSpeed = 3, fAllValueFunc = fAllBindingsAccessor(), selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '', - droppable = selector ? document.querySelector(selector) : null, + droppable = selector ? doc.querySelector(selector) : null, conf = { distance: 20, handle: '.dragHandle', @@ -352,7 +353,7 @@ ko.bindingHandlers.saveTrigger = { $el.data( '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')) { diff --git a/dev/Knoin/AbstractViewNext.js b/dev/Knoin/AbstractViewNext.js index b48c3eb9f..483f997bb 100644 --- a/dev/Knoin/AbstractViewNext.js +++ b/dev/Knoin/AbstractViewNext.js @@ -53,7 +53,7 @@ export class AbstractViewNext { closeCommand() {} // eslint-disable-line no-empty-function querySelector(selectors) { - return this.viewModelDom[0].querySelector(selectors); + return this.viewModelDom.querySelector(selectors); } } diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index 1d5ee33b3..90eed9cea 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -7,7 +7,8 @@ import { $htmlCL, VIEW_MODELS } from 'Common/Globals'; let currentScreen = null, defaultScreenName = ''; -const SCREENS = {}, $ = jQuery, +const SCREENS = {}, + qs = s => document.querySelector(s), isNonEmptyArray = values => Array.isArray(values) && values.length, popupVisibilityNames = ko.observableArray([]); @@ -29,10 +30,8 @@ export const ViewType = { * @returns {void} */ export function hideLoading() { - $('#rl-content').addClass('rl-content-show'); - $('#rl-loading') - .hide() - .remove(); + qs('#rl-content').classList.add('rl-content-show'); + qs('#rl-loading').remove(); } /** @@ -156,7 +155,7 @@ export function buildViewModel(ViewModelClass, vmScreen) { let vmDom = null; const vm = new ViewModelClass(vmScreen), position = ViewModelClass.__type || '', - vmPlace = position ? $('#rl-content #rl-' + position.toLowerCase()) : null; + vmPlace = position ? qs('#rl-content #rl-' + position.toLowerCase()) : null; ViewModelClass.__builded = true; ViewModelClass.__vm = vm; @@ -166,15 +165,14 @@ export function buildViewModel(ViewModelClass, vmScreen) { vm.viewModelTemplateID = ViewModelClass.__templateID; vm.viewModelPosition = ViewModelClass.__type; - if (vmPlace && 1 === vmPlace.length) { - vmDom = $('
') - .addClass('rl-view-model') - .addClass('RL-' + vm.viewModelTemplateID) - .hide(); - vmDom.appendTo(vmPlace); + if (vmPlace) { + vmDom = jQuery('
'); + vmDom[0].classList.add('rl-view-model', 'RL-' + vm.viewModelTemplateID); + vmDom[0].hidden = true; + vmPlace.append(vmDom[0]); - vm.viewModelDom = vmDom; - ViewModelClass.__dom = vmDom; + vm.viewModelDom = vmDom[0]; + ViewModelClass.__dom = vmDom[0]; if (ViewType.Popup === position) { vm.cancelCommand = vm.closeCommand = createCommand(() => { @@ -183,11 +181,11 @@ export function buildViewModel(ViewModelClass, vmScreen) { vm.modalVisibility.subscribe((value) => { if (value) { - vm.viewModelDom.show(); + vm.viewModelDom.hidden = false; vm.storeAndSetKeyScope(); 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); } else { @@ -197,9 +195,9 @@ export function buildViewModel(ViewModelClass, vmScreen) { vm.restoreKeyScope(); 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); // if (!bMobileDevice) { - const af = ModalView.__dom[0].querySelector('[autofocus]'); + const af = ModalView.__dom.querySelector('[autofocus]'); af && af.focus(); } } @@ -327,7 +325,7 @@ export function screenOnRoute(screenName, subPart) { ViewModelClass.__dom && ViewType.Popup !== ViewModelClass.__vm.viewModelPosition ) { - ViewModelClass.__dom.hide(); + ViewModelClass.__dom.hidden = true; ViewModelClass.__vm.viewModelVisibility(false); ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide(); @@ -353,13 +351,13 @@ export function screenOnRoute(screenName, subPart) { ) { ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow(); - ViewModelClass.__dom.show(); + ViewModelClass.__dom.hidden = false; ViewModelClass.__vm.viewModelVisibility(true); ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow(); // if (!bMobileDevice) { - const af = ViewModelClass.__dom[0].querySelector('[autofocus]'); + const af = ViewModelClass.__dom.querySelector('[autofocus]'); af && af.focus(); ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200); diff --git a/dev/Model/Message.js b/dev/Model/Message.js index f89e1b550..7e6d13046 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -5,7 +5,8 @@ import { i18n } from 'Common/Translator'; import { pInt, - previewMessage, + deModule, + encodeHtml, friendlySize, isNonEmptyArray } from 'Common/Utils'; @@ -20,7 +21,7 @@ import { emailArrayFromJson, emailArrayToStringClear, emailArrayToString, replyH import { AttachmentModel, staticCombinedIconClass } from 'Model/Attachment'; import { AbstractModel } from 'Knoin/AbstractModel'; -const $ = jQuery, isArray = Array.isArray; +const isArray = Array.isArray; class MessageModel extends AbstractModel { constructor() { @@ -604,13 +605,6 @@ class MessageModel extends AbstractModel { return [toResult, ccResult]; } - /** - * @returns {string} - */ - textBodyToString() { - return this.body ? this.body.html() : ''; - } - /** * @returns {string} */ @@ -625,24 +619,30 @@ class MessageModel extends AbstractModel { viewPopupMessage(print = false) { const timeStampInUTC = this.dateTimeStampInUTC() || 0, 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( - { - title: this.subject(), - subject: this.subject(), - date: m ? m.format('LLL') : '', - fromCreds: this.fromToLine(false), - toLabel: i18n('MESSAGE/LABEL_TO'), - toCreds: this.toToLine(false), - ccClass: ccLine ? '' : 'rl-preview-hide', - ccLabel: i18n('MESSAGE/LABEL_CC'), - ccCreds: ccLine - }, - this.body, - this.isHtml(), - print + doc.write( + deModule(require('Html/PreviewMessage.html')) + .replace('{{title}}', encodeHtml(this.subject())) + .replace('{{subject}}', encodeHtml(this.subject())) + .replace('{{date}}', encodeHtml(m ? m.format('LLL') : '')) + .replace('{{fromCreds}}', encodeHtml(this.fromToLine(false))) + .replace('{{toCreds}}', encodeHtml(this.toToLine(false))) + .replace('{{toLabel}}', encodeHtml(i18n('MESSAGE/LABEL_TO'))) + .replace('{{ccClass}}', encodeHtml(ccLine ? '' : 'rl-preview-hide')) + .replace('{{ccCreds}}', encodeHtml(ccLine)) + .replace('{{ccLabel}}', encodeHtml(i18n('MESSAGE/LABEL_CC'))) + .replace('{{bodyClass}}', this.isHtml() ? 'html' : 'plain') + .replace('{{html}}', this.bodyAsHTML()) ); + + doc.close(); + + if (print) { + setTimeout(() => win.print(), 100); + } } printMessage() { @@ -725,74 +725,60 @@ class MessageModel extends AbstractModel { return this; } - showExternalImages(lazy = false) { - if (this.body && this.body.data('rl-has-images')) { + showExternalImages() { + if (this.body && this.body.rlHasImages) { 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'; - $('[' + attr + ']', this.body).each(function() { - const $this = $(this); // eslint-disable-line no-invalid-this - if (lazy && $this.is('img')) { - $this.attr('loading', 'lazy'); + let body = this.body, attr = this.proxy ? 'data-x-additional-src' : 'data-x-src'; + body.querySelectorAll('[' + attr + ']').forEach(node => { + if (node.matches('img')) { + node.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.body).each(function() { - const $this = $(this); // eslint-disable-line no-invalid-this - let style = $this.attr('style').trim(); - style = style ? (';' === style.substr(-1) ? style + ' ' : style + '; ') : ''; - $this.attr('style', style + $this.attr(attr)); + body.querySelectorAll('[' + attr + ']').forEach(node => { + node.setAttribute('style', ((node.getAttribute('style')||'') + + ';' + node.getAttribute(attr)) + .replace(/^[;\s]+/,'')); }); } } - showInternalImages(lazy = false) { - if (this.body && !this.body.data('rl-init-internal-images')) { - this.body.data('rl-init-internal-images', true); + showInternalImages() { + if (this.body && !this.body.rlInitInternalImages) { + this.body.rlInitInternalImages = true; - const self = this; - - $('[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')); + const body = this.body; + body.querySelectorAll('[data-x-src-cid]').forEach(node => { + const attachment = this.findAttachmentByCid(node.dataset.xSrcCid); if (attachment && attachment.download) { - $this.attr('src', attachment.linkPreview()); + node.src = attachment.linkPreview(); } }); - $('[data-x-src-location]', this.body).each(function() { - const $this = $(this); // eslint-disable-line no-invalid-this - let attachment = self.findAttachmentByContentLocation($this.attr('data-x-src-location')); - if (!attachment) { - attachment = self.findAttachmentByCid($this.attr('data-x-src-location')); - } - + body.querySelectorAll('[data-x-src-location]').forEach(node => { + const attachment = this.findAttachmentByContentLocation(node.dataset.xSrcLocation) + || this.findAttachmentByCid(node.dataset.xSrcLocation); if (attachment && attachment.download) { - if (lazy && $this.is('img')) { - $this.attr('loading', 'lazy'); + if (node.matches('img')) { + node.loading = 'lazy'; } - $this.attr('src', attachment.linkPreview()); + node.src = attachment.linkPreview(); } }); - $('[data-x-style-cid]', this.body).each(function() { - let style = '', - name = ''; - - const $this = $(this), // eslint-disable-line no-invalid-this - attachment = self.findAttachmentByCid($this.attr('data-x-style-cid')); - - 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() + "')"); - } + body.querySelectorAll('[style-cid]').forEach(node => { + const name = node.dataset.xStyleCidName, + attachment = this.findAttachmentByCid(node.dataset.xStyleCid); + if (attachment && attachment.linkPreview && name) { + node.setAttribute('style', ((node.getAttribute('style')||'') + + ';' + name + ": url('" + attachment.linkPreview() + "')") + .replace(/^[;\s]+/,'')); } }); } @@ -800,22 +786,37 @@ class MessageModel extends AbstractModel { storeDataInDom() { if (this.body) { - this.body.data('rl-is-html', !!this.isHtml()); - this.body.data('rl-has-images', !!this.hasImages()); + this.body.rlIsHtml = !!this.isHtml(); + this.body.rlHasImages = !!this.hasImages(); } } fetchDataFromDom() { if (this.body) { - this.isHtml(!!this.body.data('rl-is-html')); - this.hasImages(!!this.body.data('rl-has-images')); + this.isHtml(!!this.body.rlIsHtml); + this.hasImages(!!this.body.rlHasImages); } } - replacePlaneTextBody(plain) { + /** + * @returns {string} + */ + bodyAsHTML() { 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 ''; } /** diff --git a/dev/Remote/AbstractAjax.js b/dev/Remote/AbstractAjax.js index 42d181f1c..e4c0fd019 100644 --- a/dev/Remote/AbstractAjax.js +++ b/dev/Remote/AbstractAjax.js @@ -116,7 +116,6 @@ class AbstractAjaxRemote { * @param {?number=} iTimeOut = 20000 * @param {string=} sGetAdd = '' * @param {Array=} aAbortActions = [] - * @returns {jQuery.jqXHR} */ ajaxRequest(fResultCallback, params, iTimeOut = 20000, sGetAdd = '', abortActions = []) { params = params || {}; diff --git a/dev/Screen/AbstractSettings.js b/dev/Screen/AbstractSettings.js index f6e3c88fd..ce5d59ca4 100644 --- a/dev/Screen/AbstractSettings.js +++ b/dev/Screen/AbstractSettings.js @@ -34,7 +34,6 @@ class AbstractSettingsScreen extends AbstractScreen { onRoute(subName) { let settingsScreen = null, RoutedSettingsViewModel = null, - viewModelPlace = null, viewModelDom = null; RoutedSettingsViewModel = VIEW_MODELS.settings.find( @@ -67,20 +66,19 @@ class AbstractSettingsScreen extends AbstractScreen { if (RoutedSettingsViewModel.__builded && RoutedSettingsViewModel.__vm) { settingsScreen = RoutedSettingsViewModel.__vm; } else { - viewModelPlace = this.oViewModelPlace; - if (viewModelPlace && 1 === viewModelPlace.length) { + if (this.oViewModelPlace) { settingsScreen = new RoutedSettingsViewModel(); - viewModelDom = jQuery('
') - .addClass('rl-settings-view-model') - .hide(); - viewModelDom.appendTo(viewModelPlace); + viewModelDom = jQuery('
'); + viewModelDom[0].classList.add('rl-settings-view-model'); + viewModelDom[0].hidden = true; + this.oViewModelPlace.append(viewModelDom[0]); - settingsScreen.viewModelDom = viewModelDom; + settingsScreen.viewModelDom = viewModelDom[0]; settingsScreen.__rlSettingsData = RoutedSettingsViewModel.__rlSettingsData; - RoutedSettingsViewModel.__dom = viewModelDom; + RoutedSettingsViewModel.__dom = viewModelDom[0]; RoutedSettingsViewModel.__builded = true; RoutedSettingsViewModel.__vm = settingsScreen; @@ -106,7 +104,7 @@ class AbstractSettingsScreen extends AbstractScreen { // hide if (o.oCurrentSubScreen) { o.oCurrentSubScreen.onHide && o.oCurrentSubScreen.onHide(); - o.oCurrentSubScreen.viewModelDom.hide(); + o.oCurrentSubScreen.viewModelDom.hidden = true; } // -- @@ -115,7 +113,7 @@ class AbstractSettingsScreen extends AbstractScreen { // show if (o.oCurrentSubScreen) { o.oCurrentSubScreen.onBeforeShow && o.oCurrentSubScreen.onBeforeShow(); - o.oCurrentSubScreen.viewModelDom.show(); + o.oCurrentSubScreen.viewModelDom.hidden = false; o.oCurrentSubScreen.onShow && o.oCurrentSubScreen.onShow(); 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); @@ -140,7 +138,7 @@ class AbstractSettingsScreen extends AbstractScreen { onHide() { if (this.oCurrentSubScreen && this.oCurrentSubScreen.viewModelDom) { 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() { diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index e707dd139..9492c3331 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -128,7 +128,7 @@ class MailBoxUserScreen extends AbstractScreen { , 1); } - $html.on('click', '#rl-right', () => { + jQuery($html).on('click', '#rl-right', () => { moveAction(false); }); } diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index a18f04f89..7fb88d473 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -6,7 +6,8 @@ import { pInt, pString, plainToHtml, - findEmailAndLinks + findEmailAndLinks, + htmlToElement } from 'Common/Utils'; import { @@ -44,25 +45,17 @@ import { getApp } from 'Helper/Apps/User'; import Remote from 'Remote/User/Ajax'; const - $ = jQuery, - $div = $('
'), - $hcont = $('
'), - getRealHeight = $el => { - $el - .clone() - .show() - .appendTo($hcont); - const result = $hcont.height(); - $hcont.empty(); + hcont = htmlToElement('
'), + getRealHeight = el => { + hcont.innerHTML = el.outerHTML; + const result = hcont.clientHeight; + hcont.innerHTML = ''; return result; }; let iMessageBodyCacheCount = 0; -$hcont - .attr('area', 'hidden') - .css({ position: 'absolute', left: -5000 }) - .appendTo($('body')); +document.body.append(hcont); class MessageUserStore { constructor() { @@ -218,12 +211,6 @@ class MessageUserStore { this.messageLoading.subscribe(value => this.messageLoadingThrottle(value)); - this.messagesBodiesDom.subscribe((dom) => { - if (dom && !(dom instanceof $)) { - this.messagesBodiesDom($(dom)); - } - }); - this.messageListEndFolder.subscribe(folder => { const message = this.message(); if (message && folder && folder !== message.folderFullNameRaw) { @@ -233,22 +220,20 @@ class MessageUserStore { } purgeMessageBodyCache() { - let count = 0; const end = iMessageBodyCacheCount - MESSAGE_BODY_CACHE_LIMIT; - if (0 < end) { + let count = 0; const messagesDom = this.messagesBodiesDom(); if (messagesDom) { - messagesDom.find('.rl-cache-class').each(function() { - const item = $(this); // eslint-disable-line no-invalid-this - if (end > item.data('rl-cache-count')) { - item.addClass('rl-cache-purge'); - count += 1; + messagesDom.querySelectorAll('.rl-cache-class').forEach(node => { + if (end > node.rlCacheCount) { + node.classList.add('rl-cache-purge'); + ++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() { const messagesDom = this.messagesBodiesDom(); - if (messagesDom) { - messagesDom.find('.b-text-part').hide(); - } + messagesDom && messagesDom.querySelectorAll('.b-text-part').forEach(el => el.hidden = true); } /** @@ -441,30 +424,17 @@ class MessageUserStore { * @param {Object} messageTextBody */ initBlockquoteSwitcher(messageTextBody) { - if (messageTextBody) { - const $oList = $('blockquote:not(.rl-bq-switcher)', messageTextBody).filter(function() { - return !$(this).parent().closest('blockquote', messageTextBody).length; - }); - - if ($oList && $oList.length) { - $oList.each(function() { - const $this = $(this); // eslint-disable-line no-invalid-this - - let h = $this.height() || getRealHeight($this); - - if ($this.text().trim() && (0 === h || 100 < h)) { - $this.addClass('rl-bq-switcher hidden-bq'); - $('') - .insertBefore($this) - .on('click.rlBlockquoteSwitcher', () => { - $this.toggleClass('hidden-bq'); - }) - .after('
') - .before('
'); - } - }); + messageTextBody && messageTextBody.querySelectorAll('blockquote:not(.rl-bq-switcher)').forEach(node => { + if (node.textContent.trim() && !node.parentNode.closest('blockquote')) { + let h = node.clientHeight || getRealHeight(node); + if (0 === h || 100 < h) { + const el = htmlToElement('•••'); + node.classList.add('rl-bq-switcher','hidden-bq'); + node.before(el); + el.addEventListener('click', () => node.classList.toggle('hidden-bq')); + } } - } + }); } /** @@ -472,11 +442,9 @@ class MessageUserStore { * @param {Object} message */ initOpenPgpControls(messageTextBody, message) { - if (messageTextBody && messageTextBody.find) { - messageTextBody.find('.b-plain-openpgp:not(.inited)').each(function() { - PgpStore.initMessageBodyControls($(this), message); // eslint-disable-line no-invalid-this - }); - } + messageTextBody && messageTextBody.querySelectorAll('.b-plain-openpgp:not(.inited)').forEach(node => + PgpStore.initMessageBodyControls(node, message) + ); } setMessage(data, cached) { @@ -521,19 +489,22 @@ class MessageUserStore { message.initFlagsByJson(data.Result); } - messagesDom = messagesDom && messagesDom[0] ? messagesDom : null; if (messagesDom) { id = 'rl-mgs-' + message.hash.replace(/[^a-zA-Z0-9]/g, ''); - const textBody = messagesDom.find('#' + id); - if (!textBody || !textBody[0]) { + const textBody = document.getElementById(id); + if (textBody) { + iMessageBodyCacheCount += 1; + textBody.rlCacheCount = iMessageBodyCacheCount; + message.fetchDataFromDom(); + } else { let isHtml = false; if (data.Result.Html) { isHtml = true; resultHtml = data.Result.Html.toString(); } else if (data.Result.Plain) { isHtml = false; - resultHtml = plainToHtml(data.Result.Plain.toString(), false); + resultHtml = plainToHtml(data.Result.Plain.toString()); if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpStore.capaOpenPGP()) { plain = pString(data.Result.Plain); @@ -544,16 +515,17 @@ class MessageUserStore { /-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain); } - $div.empty(); + const pre = document.createElement('pre'); if (pgpSigned && message.isPgpSigned()) { - resultHtml = $div.append($('
').text(plain)).html();
+									pre.className = 'b-plain-openpgp signed';
+									pre.textContent = plain;
 								} else if (isPgpEncrypted && message.isPgpEncrypted()) {
-									resultHtml = $div.append($('
').text(plain)).html();
+									pre.className = 'b-plain-openpgp encrypted';
+									pre.textContent = plain;
 								} else {
-									resultHtml = '
' + resultHtml + '
'; + pre.innerHTML = resultHtml; } - - $div.empty(); + resultHtml = pre.outerHTML; message.isPgpSigned(pgpSigned); message.isPgpEncrypted(isPgpEncrypted); @@ -567,41 +539,31 @@ class MessageUserStore { iMessageBodyCacheCount += 1; - body = $('
') - .hide() - .addClass('rl-cache-class'); - body.data('rl-cache-count', iMessageBodyCacheCount); - - body.html(findEmailAndLinks(resultHtml)).addClass('b-text-part ' + (isHtml ? 'html' : 'plain')); + body = htmlToElement(''); + body.rlCacheCount = iMessageBodyCacheCount; message.isHtml(!!isHtml); message.hasImages(!!data.Result.HasExternals); - message.body = body; - if (message.body) { - messagesDom.append(message.body); - } + messagesDom.append(body); message.storeDataInDom(); if (data.Result.HasInternals) { - message.showInternalImages(true); + message.showInternalImages(); } if (message.hasImages() && SettingsStore.showImages()) { - message.showExternalImages(true); + message.showExternalImages(); } 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.hideMessageBodies(); @@ -612,7 +574,7 @@ class MessageUserStore { this.initBlockquoteSwitcher(body); } - message.body.show(); + message.body.hidden = false; } initMessageFlagsFromCache(message); diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 9c7def9ce..28b5d1fe8 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -1,12 +1,134 @@ import ko from 'ko'; 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 { 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 { constructor() { this.capaOpenPGP = ko.observable(false); @@ -209,174 +331,34 @@ class PgpUserStore { 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 {MessageModel} rainLoopMessage */ initMessageBodyControls(dom, rainLoopMessage) { - if (dom && !dom.hasClass('inited')) { - dom.addClass('inited'); + const cl = dom && dom.classList; + if (!cl.has('inited')) { + cl.add('inited'); - const encrypted = dom.hasClass('encrypted'), - signed = dom.hasClass('signed'), + const encrypted = cl.has('encrypted'), + signed = cl.has('signed'), recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : []; let verControl = null; if (encrypted || signed) { - const domText = dom.text(); - dom.data('openpgp-original', domText); + const domText = dom.textContent; + verControl = htmlToElement('
'); // 🔒 if (encrypted) { - verControl = jQuery('
') - .attr('title', i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC')) - .on('click', PgpUserStore.domControlEncryptedClickHelper(this, dom, domText, recipients)); - } else if (signed) { - verControl = jQuery('
') - .attr('title', i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC')) - .on('click', PgpUserStore.domControlSignedClickHelper(this, dom, domText)); + verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC'); + verControl.addEventHandler('click', domControlEncryptedClickHelper(this, dom, domText, recipients)); + } else { + verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC'); + verControl.addEventHandler('click', domControlSignedClickHelper(this, dom, domText)); } - if (verControl) { - dom.before(verControl).before('
'); - } + dom.before(verControl, document.createElement('div')); } } } diff --git a/dev/Stores/User/Settings.js b/dev/Stores/User/Settings.js index 991839343..2115d62ee 100644 --- a/dev/Stores/User/Settings.js +++ b/dev/Stores/User/Settings.js @@ -2,7 +2,6 @@ import ko from 'ko'; import { MESSAGES_PER_PAGE, MESSAGES_PER_PAGE_VALUES } from 'Common/Consts'; import { Layout, EditorDefaultType } from 'Common/Enums'; -import { $htmlCL } from 'Common/Globals'; import { pInt } from 'Common/Utils'; import * as Settings from 'Storage/Settings'; @@ -43,10 +42,11 @@ class SettingsUserStore { } subscribers() { - this.layout.subscribe((value) => { - $htmlCL.toggle('rl-no-preview-pane', Layout.NoPreview === value); - $htmlCL.toggle('rl-side-preview-pane', Layout.SidePreview === value); - $htmlCL.toggle('rl-bottom-preview-pane', Layout.BottomPreview === value); + const htmlCL = document.documentElement.classList; + this.layout.subscribe(value => { + htmlCL.toggle('rl-no-preview-pane', Layout.NoPreview === 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})); }); } diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less index 841c61028..4fc74e60a 100644 --- a/dev/Styles/MessageView.less +++ b/dev/Styles/MessageView.less @@ -376,13 +376,13 @@ html.rl-no-preview-pane { background-color: #eee; border: 1px solid #999; - display: inline-block; + display: block; width: 30px; height: 14px; line-height: 14px; text-align: center; cursor: pointer; - margin: 10px 0px; + margin: 2em 0 10px; opacity: 0.5; &:hover { diff --git a/dev/View/Admin/Login.js b/dev/View/Admin/Login.js index 40804ccdc..41cb50884 100644 --- a/dev/View/Admin/Login.js +++ b/dev/View/Admin/Login.js @@ -1,7 +1,5 @@ import ko from 'ko'; -import { triggerAutocompleteInputChange } from 'Common/Utils'; - import { StorageResultType, Notification } from 'Common/Enums'; import { getNotification } from 'Common/Translator'; @@ -37,8 +35,6 @@ class LoginAdminView extends AbstractViewNext { this.loginErrorAnimation = 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.formError = ko.computed(() => this.loginErrorAnimation() || this.passwordErrorAnimation()); @@ -59,8 +55,6 @@ class LoginAdminView extends AbstractViewNext { @command((self) => !self.submitRequest()) submitCommand() { - triggerAutocompleteInputChange(); - this.loginError(false); this.passwordError(false); @@ -98,13 +92,6 @@ class LoginAdminView extends AbstractViewNext { routeOff(); } - onHide() { - } - - onBuild() { - triggerAutocompleteInputChange(true); - } - submitForm() { this.submitCommand(); } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index fc15d56d5..ed654d6ed 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -13,7 +13,6 @@ import { import { isNonEmptyArray, - clearBqSwitcher, replySubjectAdd, encodeHtml, inFocus, @@ -855,13 +854,7 @@ class ComposePopupView extends AbstractViewNext { sDate = momentorFormat(message.dateTimeStampInUTC(), 'FULL'); sSubject = message.subject(); aDraftInfo = message.aDraftInfo; - - const clonedText = jQuery(message.body).clone(); - if (clonedText) { - clearBqSwitcher(clonedText); - - sText = clonedText.html(); - } + sText = message.bodyAsHTML(); let resplyAllParts = null; switch (lineComposeType) { diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js index 7d0524459..34d49e6b5 100644 --- a/dev/View/Popup/Contacts.js +++ b/dev/View/Popup/Contacts.js @@ -610,9 +610,7 @@ class ContactsPopupView extends AbstractViewNext { } onBuild(dom) { - this.oContentVisible = jQuery('.b-list-content', dom); - - this.selector.init(this.oContentVisible, this.oContentVisible, KeyState.ContactList); + this.selector.init(dom[0].querySelector('.b-list-content'), KeyState.ContactList); key('delete', KeyState.ContactList, () => { this.deleteCommand(); diff --git a/dev/View/Popup/MessageOpenPgp.js b/dev/View/Popup/MessageOpenPgp.js index fb12b4f69..0e37ee867 100644 --- a/dev/View/Popup/MessageOpenPgp.js +++ b/dev/View/Popup/MessageOpenPgp.js @@ -120,10 +120,8 @@ class MessageOpenPgpPopupView extends AbstractViewNext { this.privateKeys(privateKeys); if (this.viewModelDom) { - this.viewModelDom - .find('.key-list__item') - .first() - .click(); + const el = this.viewModelDom.querySelector('.key-list__item'); + el && el.click(); } } } diff --git a/dev/View/User/Login.js b/dev/View/User/Login.js index 35b73e28f..8df922182 100644 --- a/dev/View/User/Login.js +++ b/dev/View/User/Login.js @@ -8,7 +8,7 @@ import { Notification } from 'Common/Enums'; -import { convertLangName, triggerAutocompleteInputChange } from 'Common/Utils'; +import { convertLangName } from 'Common/Utils'; import { getNotification, getNotificationFromResponse, reload as translatorReload } from 'Common/Translator'; @@ -72,9 +72,6 @@ class LoginUserView extends AbstractViewNext { (this.additionalCode.visibility() && this.additionalCode.errorAnimation()) ); -// this.emailFocus = ko.observable(false); -// this.passwordFocus = ko.observable(false); - this.email.subscribe(() => { this.emailError(false); this.additionalCode(''); @@ -144,8 +141,6 @@ class LoginUserView extends AbstractViewNext { @command((self) => !self.submitRequest()) submitCommand() { - triggerAutocompleteInputChange(); - this.emailError(false); this.passwordError(false); @@ -280,8 +275,6 @@ class LoginUserView extends AbstractViewNext { ); }); }, 50); - - triggerAutocompleteInputChange(true); } submitForm() { @@ -291,16 +284,6 @@ class LoginUserView extends AbstractViewNext { selectLanguage() { 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 }; diff --git a/dev/View/User/MailBox/FolderList.js b/dev/View/User/MailBox/FolderList.js index 7a42d7c4f..a326d0948 100644 --- a/dev/View/User/MailBox/FolderList.js +++ b/dev/View/User/MailBox/FolderList.js @@ -17,8 +17,6 @@ import { getApp } from 'Helper/Apps/User'; import { view, ViewType, showScreenPopup, setHash } from 'Knoin/Knoin'; import { AbstractViewNext } from 'Knoin/AbstractViewNext'; -const $ = jQuery; - @view({ name: 'View/User/MailBox/FolderList', type: ViewType.Left, @@ -28,7 +26,6 @@ class FolderListMailBoxUserView extends AbstractViewNext { constructor() { super(); - this.oContentVisible = null; this.oContentScrollable = null; this.composeInEdit = AppStore.composeInEdit; @@ -61,10 +58,8 @@ class FolderListMailBoxUserView extends AbstractViewNext { } onBuild(dom) { - this.oContentVisible = $('.b-content', dom); - this.oContentScrollable = this.oContentVisible ? this.oContentVisible[0] : null; - const self = this, + qs = s => dom[0].querySelector(s), isMobile = Settings.appSettingsGet('mobile'), fSelectFolder = (el, event, starred) => { const isMove = moveAction(); @@ -108,6 +103,8 @@ class FolderListMailBoxUserView extends AbstractViewNext { } }; + this.oContentScrollable = qs('.b-content'); + dom .on('click', '.b-folders .e-item .e-link .e-collapsed-sign', function(event) { // eslint-disable-line prefer-arrow-callback @@ -132,21 +129,22 @@ class FolderListMailBoxUserView extends AbstractViewNext { key('up, down', KeyState.FolderList, (event, handler) => { const keyCode = handler && 'up' === handler.shortcut ? EventKeyCode.Up : EventKeyCode.Down, - $items = $('.b-folders .e-item .e-link:not(.hidden):visible', dom); - - if (event && $items.length) { - let index = $items.index($items.filter('.focused')); - if (-1 < index) { - $items.eq(index).removeClass('focused'); + $items = jQuery('.b-folders .e-item .e-link:not(.hidden):visible', dom); + let index = $items.length; + if (event && index) { + while (index--) { + if ($items[index].matches('.focused')) { + $items[index].classList.remove('focused'); + break; + } } - if (EventKeyCode.Up === keyCode && 0 < index) { - index -= 1; + --index; } else if (EventKeyCode.Down === keyCode && index < $items.length - 1) { - index += 1; + ++index; } - $items.eq(index).addClass('focused'); + $items[index].classList.add('focused'); self.scrollToFocused(); } @@ -154,24 +152,22 @@ class FolderListMailBoxUserView extends AbstractViewNext { }); key('enter', KeyState.FolderList, () => { - const $items = $('.b-folders .e-item .e-link:not(.hidden).focused', dom); - if ($items.length && $items[0]) { + const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'); + if (item) { AppStore.focusedState(Focused.MessageList); - $items.click(); + item.click(); } return false; }); key('space', KeyState.FolderList, () => { - const $items = $('.b-folders .e-item .e-link:not(.hidden).focused', dom); - if ($items.length && $items[0]) { - const folder = ko.dataFor($items[0]); - if (folder) { - const collapsed = folder.collapsed(); - getApp().setExpandedFolder(folder.fullNameHash, collapsed); - folder.collapsed(!collapsed); - } + const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'), + folder = item && ko.dataFor(item); + if (folder) { + const collapsed = folder.collapsed(); + getApp().setExpandedFolder(folder.fullNameHash, collapsed); + folder.collapsed(!collapsed); } return false; @@ -183,10 +179,12 @@ class FolderListMailBoxUserView extends AbstractViewNext { return false; }); - AppStore.focusedState.subscribe((value) => { - $('.b-folders .e-item .e-link.focused', dom).removeClass('focused'); + AppStore.focusedState.subscribe(value => { + 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) { - $('.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() { - if (!this.oContentVisible || !this.oContentScrollable) { - return false; - } - - 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; + const scrollable = this.oContentScrollable; + 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'); } - - return true; } - - return false; } /** @@ -237,9 +227,9 @@ class FolderListMailBoxUserView extends AbstractViewNext { */ messagesDrop(toFolder, ui) { if (toFolder && ui && ui.helper) { - const fromFolderFullNameRaw = ui.helper.data('rl-folder'), + const fromFolderFullNameRaw = ui.helper.rlFolder, copy = $htmlCL.contains('rl-ctrl-key-pressed'), - uids = ui.helper.data('rl-uids'); + uids = ui.helper.rlUids; if (fromFolderFullNameRaw && Array.isArray(uids)) { getApp().moveMessagesToFolder(fromFolderFullNameRaw, uids, toFolder.fullNameRaw, copy); diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index 35a67cbc7..1659f9dcf 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -15,7 +15,7 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts'; 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 { Selector } from 'Common/Selector'; @@ -267,9 +267,9 @@ class MessageListMailBoxUserView extends AbstractViewNext { } }); - MessageStore.messageListEndHash.subscribe(() => { - this.selector.scrollToTop(); - }); + MessageStore.messageListEndHash.subscribe((() => + this.selector.scrollToFocused() + ).throttle(50)); } @command() @@ -502,14 +502,20 @@ class MessageListMailBoxUserView extends AbstractViewNext { oMessageListItem.checked(true); } - const el = draggablePlace(), + const el = htmlToElement('
' + + ' ' + + '' + + '' + + '
'), updateUidsInfo = () => { const uids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails(); - el.data('rl-uids', uids); - el.find('.text').text('' + uids.length); + el.rlUids = uids; + el.querySelector('.text').textContent = uids.length; }; - el.data('rl-folder', FolderStore.currentFolderFullNameRaw()); + document.getElementById('rl-hidden').append(el); + + el.rlFolder = FolderStore.currentFolderFullNameRaw(); updateUidsInfo(); setTimeout(updateUidsInfo,1); @@ -735,9 +741,7 @@ class MessageListMailBoxUserView extends AbstractViewNext { onBuild(dom) { const self = this; - this.oContentVisible = jQuery('.b-content', dom); - - this.selector.init(this.oContentVisible, this.oContentVisible, KeyState.MessageList); + this.selector.init(dom[0].querySelector('.b-content'), KeyState.MessageList); if (this.mobile) { dom.on('click', () => { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 9cbd4fd7c..eef7b7e05 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -243,8 +243,8 @@ class MessageViewMailBoxUserView extends AbstractViewNext { return ''; }); - this.messageActiveDom.subscribe((dom) => { - this.bodyBackgroundColor(dom ? this.detectDomBackgroundColor(dom) : ''); + this.messageActiveDom.subscribe(dom => { + this.bodyBackgroundColor(this.detectDomBackgroundColor(dom)); }, this); this.message.subscribe((message) => { @@ -357,45 +357,26 @@ class MessageViewMailBoxUserView extends AbstractViewNext { } detectDomBackgroundColor(dom) { - let limit = 5, - result = ''; + let color = ''; - 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 = ''; - if (inputDom) { - color = inputDom.css('background-color') || ''; - if (!inputDom.is('table')) { - color = isTransparent(color) ? '' : color; - } - } + if (dom) { + 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; - 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; + aC = children[0]; + color = aC.style.backgroundColor || ''; + if (!aC.matches('table')) { + color = isTransparent(color) ? '' : color; } } - result = isTransparent(result) ? '' : result; + color = isTransparent(color) ? '' : color; } - return result; + return color; } fullScreen() { @@ -545,7 +526,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext { !!event && 3 !== event.which && mailToHelper( - jQuery(this).attr('href'), + this.href, 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 key('b', [KeyState.MessageList, KeyState.MessageView], () => { - if (MessageStore.message() && MessageStore.message().body) { - MessageStore.message() - .body.find('.rlBlockquoteSwitcher') - .click(); + const message = MessageStore.message(); + if (message && message.body) { + message.body.querySelectorAll('.rlBlockquoteSwitcher').forEach(node => node.click()); return false; } - return true; }); @@ -878,7 +857,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext { */ showImages(message) { if (message && message.showExternalImages) { - message.showExternalImages(true); + message.showExternalImages(); } } diff --git a/dev/boot.js b/dev/boot.js index 91b6920f4..7cf230af1 100644 --- a/dev/boot.js +++ b/dev/boot.js @@ -16,7 +16,7 @@ progress.className = "progressjs-inner"; progress.appendChild(doc.createElement('div')).className = "progressjs-percent"; setPercentWidth(1); -doc.body.appendChild(container); +doc.body.append(container); win.progressJs = new class { set(percent) { @@ -80,7 +80,7 @@ function writeCSS(css) { const style = doc.createElement('style'); style.type = 'text/css'; style.textContent = css; - doc.head.appendChild(style); + doc.head.append(style); } function loadScript(src) { @@ -93,8 +93,8 @@ function loadScript(src) { script.onerror = () => reject(new Error(src)); script.src = src; // script.type = 'text/javascript'; - doc.head.appendChild(script); -// doc.body.appendChild(element); + doc.head.append(script); +// doc.body.append(element); }); } diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html index a35007d67..a189c25fe 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html @@ -9,7 +9,7 @@