Added: addEventsListener() and addEventsListeners()

This commit is contained in:
the-djmaze 2022-02-24 11:19:19 +01:00
parent c270f0ad9a
commit 5990addfef
14 changed files with 484 additions and 464 deletions

View file

@ -19,7 +19,8 @@ import {
$htmlCL, $htmlCL,
Settings, Settings,
SettingsGet, SettingsGet,
leftPanelDisabled leftPanelDisabled,
addEventsListener
} from 'Common/Globals'; } from 'Common/Globals';
import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { UNUSED_OPTION_VALUE } from 'Common/Consts';
@ -89,7 +90,7 @@ class AppUser extends AbstractApp {
}, interval); }, interval);
const fn = (ev=>$htmlCL.toggle('rl-ctrl-key-pressed', ev.ctrlKey)).debounce(500); const fn = (ev=>$htmlCL.toggle('rl-ctrl-key-pressed', ev.ctrlKey)).debounce(500);
['keydown','keyup'].forEach(t => doc.addEventListener(t, fn)); addEventsListener(doc, ['keydown','keyup'], fn);
shortcuts.add('escape,enter', '', Scope.All, () => rl.Dropdowns.detectVisibility()); shortcuts.add('escape,enter', '', Scope.All, () => rl.Dropdowns.detectVisibility());
} }
@ -344,9 +345,7 @@ class AppUser extends AbstractApp {
} }
// add pointermove ? // add pointermove ?
['touchstart','mousemove','keydown'].forEach( addEventsListener(doc, ['touchstart','mousemove','keydown'], SettingsUserStore.delayLogout, {passive:true});
t => doc.addEventListener(t, SettingsUserStore.delayLogout, {passive:true})
);
SettingsUserStore.delayLogout(); SettingsUserStore.delayLogout();
// initLeftSideLayoutResizer // initLeftSideLayoutResizer

View file

@ -1,5 +1,5 @@
import * as Links from 'Common/Links'; import * as Links from 'Common/Links';
import { doc, SettingsGet, fireEvent } from 'Common/Globals'; import { doc, SettingsGet, fireEvent, addEventsListener } from 'Common/Globals';
let notificator = null, let notificator = null,
player = null, player = null,
@ -73,8 +73,7 @@ export const SMAudio = new class {
this.supportedOgg = canPlay('audio/ogg; codecs="vorbis"'); this.supportedOgg = canPlay('audio/ogg; codecs="vorbis"');
if (player) { if (player) {
const stopFn = () => this.pause(); const stopFn = () => this.pause();
player.addEventListener('ended', stopFn); addEventsListener(player, ['ended','error'], stopFn);
player.addEventListener('error', stopFn);
addEventListener('audio.api.stop', stopFn); addEventListener('audio.api.stop', stopFn);
} }
} }

View file

@ -29,16 +29,10 @@ export const Capa = {
export const Scope = { export const Scope = {
All: 'all', All: 'all',
None: 'none', None: 'none',
Contacts: 'Contacts',
MessageList: 'MessageList', MessageList: 'MessageList',
FolderList: 'FolderList', FolderList: 'FolderList',
MessageView: 'MessageView', MessageView: 'MessageView',
Compose: 'Compose', Settings: 'Settings'
Settings: 'Settings',
Menu: 'Menu',
OpenPgpKey: 'OpenPgpKey',
KeyboardShortcutsHelp: 'KeyboardShortcutsHelp',
Ask: 'Ask'
}; };
/** /**

View file

@ -4,6 +4,7 @@ import { Scope } from 'Common/Enums';
let keyScopeFake = Scope.All; let keyScopeFake = Scope.All;
export const export const
ScopeMenu = 'Menu',
doc = document, doc = document,
@ -31,14 +32,20 @@ export const
fireEvent = (name, detail) => dispatchEvent(new CustomEvent(name, {detail:detail})), fireEvent = (name, detail) => dispatchEvent(new CustomEvent(name, {detail:detail})),
addEventsListener = (element, events, fn, options) =>
events.forEach(event => element.addEventListener(event, fn, options)),
addEventsListeners = (element, events) =>
Object.entries(events).forEach(([event, fn]) => element.addEventListener(event, fn)),
// keys // keys
keyScopeReal = ko.observable(Scope.All), keyScopeReal = ko.observable(Scope.All),
keyScope = value => { keyScope = value => {
if (value) { if (value) {
if (Scope.Menu !== value) { if (ScopeMenu !== value) {
keyScopeFake = value; keyScopeFake = value;
if (dropdownVisibility()) { if (dropdownVisibility()) {
value = Scope.Menu; value = ScopeMenu;
} }
} }
keyScopeReal(value); keyScopeReal(value);
@ -50,8 +57,8 @@ export const
dropdownVisibility.subscribe(value => { dropdownVisibility.subscribe(value => {
if (value) { if (value) {
keyScope(Scope.Menu); keyScope(ScopeMenu);
} else if (Scope.Menu === shortcuts.getScope()) { } else if (ScopeMenu === shortcuts.getScope()) {
keyScope(keyScopeFake); keyScope(keyScopeFake);
} }
}); });

View file

@ -1,4 +1,5 @@
import ko from 'ko'; import ko from 'ko';
import { addEventsListeners } from 'Common/Globals';
import { isArray } from 'Common/Utils'; import { isArray } from 'Common/Utils';
import { koComputable } from 'External/ko'; import { koComputable } from 'External/ko';
@ -239,27 +240,28 @@ export class Selector {
return el ? ko.dataFor(el) : null; return el ? ko.dataFor(el) : null;
}; };
contentScrollable.addEventListener('click', event => { addEventsListeners(contentScrollable, {
let el = event.target.closestWithin(this.sItemSelector, contentScrollable); click: event => {
el && this.actionClick(ko.dataFor(el), event); let el = event.target.closestWithin(this.sItemSelector, contentScrollable);
el && this.actionClick(ko.dataFor(el), event);
const item = getItem(this.sItemCheckedSelector); const item = getItem(this.sItemCheckedSelector);
if (item) {
if (event.shiftKey) {
this.actionClick(item, event);
} else {
this.focusedItem(item);
item.checked(!item.checked());
}
}
});
contentScrollable.addEventListener('auxclick', event => {
if (1 == event.button) {
const item = getItem(this.sItemSelector);
if (item) { if (item) {
this.focusedItem(item); if (event.shiftKey) {
(this.oCallbacks.MiddleClick || (()=>0))(item); this.actionClick(item, event);
} else {
this.focusedItem(item);
item.checked(!item.checked());
}
}
},
auxclick: event => {
if (1 == event.button) {
const item = getItem(this.sItemSelector);
if (item) {
this.focusedItem(item);
(this.oCallbacks.MiddleClick || (()=>0))(item);
}
} }
} }
}); });

View file

@ -1,4 +1,4 @@
import { doc, createElement } from 'Common/Globals'; import { doc, createElement, addEventsListeners } from 'Common/Globals';
import { EmailModel } from 'Model/Email'; import { EmailModel } from 'Model/Email';
const contentType = 'snappymail/emailaddress', const contentType = 'snappymail/emailaddress',
@ -41,49 +41,55 @@ export class EmailAddressesComponent {
// Create the elements // Create the elements
self.ul = createElement('ul',{class:"emailaddresses"}); self.ul = createElement('ul',{class:"emailaddresses"});
self.ul.addEventListener('click', e => self._focus(e)); addEventsListeners(self.ul, {
self.ul.addEventListener('dblclick', e => self._editTag(e)); click: e => self._focus(e),
self.ul.addEventListener("dragenter", fnDrag); dblclick: e => self._editTag(e),
self.ul.addEventListener("dragover", fnDrag); dragenter: fnDrag,
self.ul.addEventListener("drop", e => { dragover: fnDrag,
if (validDropzone() && dragAddress.value) { drop: e => {
e.preventDefault(); if (validDropzone() && dragAddress.value) {
dragAddress.source._removeDraggedTag(dragAddress.li); e.preventDefault();
self._parseValue(dragAddress.value); dragAddress.source._removeDraggedTag(dragAddress.li);
self._parseValue(dragAddress.value);
}
} }
}); });
self.input = createElement('input',{type:"text", list:datalist.id, self.input = createElement('input',{type:"text", list:datalist.id,
autocomplete:"off", autocorrect:"off", autocapitalize:"off", spellcheck:"false"}); autocomplete:"off", autocorrect:"off", autocapitalize:"off", spellcheck:"false"});
self.input.addEventListener('focus', () => self._focusTrigger(true)); addEventsListeners(self.input, {
self.input.addEventListener('blur', () => { focus: () => {
// prevent autoComplete menu click from causing a false 'blur' self._focusTrigger(true);
self._parseInput(true); self.input.value || self._resetDatalist();
self._focusTrigger(false); },
}); blur: () => {
self.input.addEventListener('keydown', e => { // prevent autoComplete menu click from causing a false 'blur'
if ('Backspace' === e.key || 'ArrowLeft' === e.key) {
// if our input contains no value and backspace has been pressed, select the last tag
var lastTag = self.inputCont.previousElementSibling,
input = self.input;
if (lastTag && (!input.value
|| (('selectionStart' in input) && input.selectionStart === 0 && input.selectionEnd === 0))
) {
e.preventDefault();
lastTag.querySelector('a').focus();
}
self._updateDatalist();
} else if (e.key == 'Enter') {
e.preventDefault();
self._parseInput(true); self._parseInput(true);
self._focusTrigger(false);
},
keydown: e => {
if ('Backspace' === e.key || 'ArrowLeft' === e.key) {
// if our input contains no value and backspace has been pressed, select the last tag
var lastTag = self.inputCont.previousElementSibling,
input = self.input;
if (lastTag && (!input.value
|| (('selectionStart' in input) && input.selectionStart === 0 && input.selectionEnd === 0))
) {
e.preventDefault();
lastTag.querySelector('a').focus();
}
self._updateDatalist();
} else if (e.key == 'Enter') {
e.preventDefault();
self._parseInput(true);
}
},
input: () => {
self._parseInput();
self._updateDatalist();
} }
}); });
self.input.addEventListener('input', () => {
self._parseInput();
self._updateDatalist();
});
self.input.addEventListener('focus', () => self.input.value || self._resetDatalist());
// define starting placeholder // define starting placeholder
if (element.placeholder) { if (element.placeholder) {
@ -261,66 +267,70 @@ export class EmailAddressesComponent {
el = createElement('a',{href:'#', class:'ficon'}); el = createElement('a',{href:'#', class:'ficon'});
el.append('✖'); el.append('✖');
el.addEventListener('click', e => self._removeTag(e, li)); addEventsListeners(el, {
el.addEventListener('focus', () => li.className = 'emailaddresses-selected'); click: e => self._removeTag(e, li),
el.addEventListener('blur', () => li.className = null); focus: () => li.className = 'emailaddresses-selected',
el.addEventListener('keydown', e => { blur: () => li.className = null,
switch (e.key) { keydown: e => {
case 'Delete': switch (e.key) {
case 'Backspace': case 'Delete':
self._removeTag(e, li); case 'Backspace':
break; self._removeTag(e, li);
break;
// 'e' - edit tag (removes tag and places value into visible input // 'e' - edit tag (removes tag and places value into visible input
case 'e': case 'e':
case 'Enter': case 'Enter':
self._editTag(e); self._editTag(e);
break; break;
case 'ArrowLeft': case 'ArrowLeft':
// select the previous tag or input if no more tags exist // select the previous tag or input if no more tags exist
var previous = el.closest('li').previousElementSibling; var previous = el.closest('li').previousElementSibling;
if (previous.matches('li')) { if (previous.matches('li')) {
previous.querySelector('a').focus(); previous.querySelector('a').focus();
} else { } else {
self.focus(); self.focus();
} }
break; break;
case 'ArrowRight': case 'ArrowRight':
// select the next tag or input if no more tags exist // select the next tag or input if no more tags exist
var next = el.closest('li').nextElementSibling; var next = el.closest('li').nextElementSibling;
if (next !== this.inputCont) { if (next !== this.inputCont) {
next.querySelector('a').focus(); next.querySelector('a').focus();
} else { } else {
this.focus(); this.focus();
} }
break; break;
case 'ArrowDown': case 'ArrowDown':
self._focus(e); self._focus(e);
break; break;
}
} }
}); });
li.append(el); li.append(el);
li.emailaddress = v; li.emailaddress = v;
li.addEventListener("dragstart", e => { addEventsListeners(li, {
dragAddress = { dragstart: e => {
source: self, dragAddress = {
li: li, source: self,
value: li.emailaddress.obj.toLine() li: li,
}; value: li.emailaddress.obj.toLine()
// e.dataTransfer.setData(contentType, li.emailaddress.obj.toLine()); };
e.dataTransfer.setData('text/plain', contentType); // e.dataTransfer.setData(contentType, li.emailaddress.obj.toLine());
// e.dataTransfer.setDragImage(li, 0, 0); e.dataTransfer.setData('text/plain', contentType);
e.dataTransfer.effectAllowed = 'move'; // e.dataTransfer.setDragImage(li, 0, 0);
li.style.opacity = 0.25; e.dataTransfer.effectAllowed = 'move';
}); li.style.opacity = 0.25;
li.addEventListener("dragend", () => { },
dragAddress = null; dragend: () => {
li.style.cssText = ''; dragAddress = null;
li.style.cssText = '';
}
}); });
self.inputCont.before(li); self.inputCont.before(li);

View file

@ -2,7 +2,7 @@ import 'External/ko';
import ko from 'ko'; import ko from 'ko';
import { HtmlEditor } from 'Common/Html'; import { HtmlEditor } from 'Common/Html';
import { timeToNode } from 'Common/Translator'; import { timeToNode } from 'Common/Translator';
import { elementById } from 'Common/Globals'; import { elementById, addEventsListeners } from 'Common/Globals';
import { isArray } from 'Common/Utils'; import { isArray } from 'Common/Utils';
import { EmailAddressesComponent } from 'Component/EmailAddresses'; import { EmailAddressesComponent } from 'Component/EmailAddresses';
import { ThemeStore } from 'Stores/Theme'; import { ThemeStore } from 'Stores/Theme';
@ -29,226 +29,236 @@ const rlContentType = 'snappymail/action',
id: 0, id: 0,
stop: () => clearTimeout(dragTimer.id), stop: () => clearTimeout(dragTimer.id),
start: fn => dragTimer.id = setTimeout(fn, 500) start: fn => dragTimer.id = setTimeout(fn, 500)
}; },
ttn = (element, fValueAccessor) => timeToNode(element, ko.unwrap(fValueAccessor()));
let dragImage, let dragImage,
dragData; dragData;
ko.bindingHandlers.editor = { Object.assign(ko.bindingHandlers, {
init: (element, fValueAccessor) => {
let editor = null;
const fValue = fValueAccessor(), editor: {
fUpdateEditorValue = () => fValue && fValue.__editor && fValue.__editor.setHtmlOrPlain(fValue()), init: (element, fValueAccessor) => {
fUpdateKoValue = () => fValue && fValue.__editor && fValue(fValue.__editor.getDataWithHtmlMark()), let editor = null;
fOnReady = () => {
fValue.__editor = editor;
fUpdateEditorValue();
};
if (ko.isObservable(fValue) && HtmlEditor) { const fValue = fValueAccessor(),
editor = new HtmlEditor(element, fUpdateKoValue, fOnReady, fUpdateKoValue); fUpdateEditorValue = () => fValue && fValue.__editor && fValue.__editor.setHtmlOrPlain(fValue()),
fUpdateKoValue = () => fValue && fValue.__editor && fValue(fValue.__editor.getDataWithHtmlMark()),
fOnReady = () => {
fValue.__editor = editor;
fUpdateEditorValue();
};
fValue.__fetchEditorValue = fUpdateKoValue; if (ko.isObservable(fValue) && HtmlEditor) {
editor = new HtmlEditor(element, fUpdateKoValue, fOnReady, fUpdateKoValue);
fValue.subscribe(fUpdateEditorValue); fValue.__fetchEditorValue = fUpdateKoValue;
// ko.utils.domNodeDisposal.addDisposeCallback(element, () => { fValue.subscribe(fUpdateEditorValue);
// });
}
}
};
let ttn = (element, fValueAccessor) => timeToNode(element, ko.unwrap(fValueAccessor())); // ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
ko.bindingHandlers.moment = { // });
init: ttn, }
update: ttn
};
ko.bindingHandlers.emailsTags = {
init: (element, fValueAccessor, fAllBindings) => {
const fValue = fValueAccessor();
element.addresses = new EmailAddressesComponent(element, {
focusCallback: value => fValue.focused && fValue.focused(!!value),
autoCompleteSource: fAllBindings.get('autoCompleteSource'),
onChange: value => fValue(value)
});
if (fValue.focused && fValue.focused.subscribe) {
fValue.focused.subscribe(value =>
element.addresses[value ? 'focus' : 'blur']()
);
} }
}, },
update: (element, fValueAccessor) => {
element.addresses.value = ko.unwrap(fValueAccessor());
}
};
// Start dragging selected messages moment: {
ko.bindingHandlers.dragmessages = { init: ttn,
init: (element, fValueAccessor) => { update: ttn
element.addEventListener("dragstart", e => { },
let data = fValueAccessor()(e);
dragImage || (dragImage = elementById('messagesDragImage'));
if (data && dragImage && !ThemeStore.isMobile()) {
dragImage.querySelector('.text').textContent = data.uids.length;
let img = dragImage.querySelector('i');
img.classList.toggle('icon-copy', e.ctrlKey);
img.classList.toggle('icon-mail', !e.ctrlKey);
// Else Chrome doesn't show it emailsTags: {
dragImage.style.left = e.clientX + 'px'; init: (element, fValueAccessor, fAllBindings) => {
dragImage.style.top = e.clientY + 'px'; const fValue = fValueAccessor();
dragImage.style.right = 'auto';
setDragAction(e, 'messages', e.ctrlKey ? 'copy' : 'move', data, dragImage); element.addresses = new EmailAddressesComponent(element, {
focusCallback: value => fValue.focused && fValue.focused(!!value),
autoCompleteSource: fAllBindings.get('autoCompleteSource'),
onChange: value => fValue(value)
});
// Remove the Chrome visibility if (fValue.focused && fValue.focused.subscribe) {
dragImage.style.cssText = ''; fValue.focused.subscribe(value =>
} else { element.addresses[value ? 'focus' : 'blur']()
e.preventDefault(); );
} }
},
update: (element, fValueAccessor) => {
element.addresses.value = ko.unwrap(fValueAccessor());
}
},
}, false); // Start dragging selected messages
element.addEventListener("dragend", () => dragData = null); dragmessages: {
element.setAttribute('draggable', true); init: (element, fValueAccessor) => {
} element.addEventListener("dragstart", e => {
}; let data = fValueAccessor()(e);
dragImage || (dragImage = elementById('messagesDragImage'));
if (data && dragImage && !ThemeStore.isMobile()) {
dragImage.querySelector('.text').textContent = data.uids.length;
let img = dragImage.querySelector('i');
img.classList.toggle('icon-copy', e.ctrlKey);
img.classList.toggle('icon-mail', !e.ctrlKey);
// Drop selected messages on folder // Else Chrome doesn't show it
ko.bindingHandlers.dropmessages = { dragImage.style.left = e.clientX + 'px';
init: (element, fValueAccessor) => { dragImage.style.top = e.clientY + 'px';
const folder = fValueAccessor(), dragImage.style.right = 'auto';
// folder = ko.dataFor(element),
fnStop = e => {
e.preventDefault();
element.classList.remove('droppableHover');
dragTimer.stop();
},
fnHover = e => {
if ('messages' === getDragAction(e)) {
fnStop(e);
element.classList.add('droppableHover');
if (folder && folder.collapsed()) {
dragTimer.start(() => {
folder.collapsed(false);
setExpandedFolder(folder.fullName, true);
}, 500);
}
}
};
element.addEventListener("dragenter", fnHover);
element.addEventListener("dragover", fnHover);
element.addEventListener("dragleave", fnStop);
element.addEventListener("drop", e => {
fnStop(e);
if ('messages' === getDragAction(e) && ['move','copy'].includes(e.dataTransfer.effectAllowed)) {
let data = dragData.data;
if (folder && data && data.folder && isArray(data.uids)) {
moveMessagesToFolder(data.folder, data.uids, folder.fullName, data.copy && e.ctrlKey);
}
}
});
}
};
ko.bindingHandlers.sortableItem = { setDragAction(e, 'messages', e.ctrlKey ? 'copy' : 'move', data, dragImage);
init: (element, fValueAccessor) => {
let options = ko.unwrap(fValueAccessor()) || {}, // Remove the Chrome visibility
parent = element.parentNode, dragImage.style.cssText = '';
fnHover = e => { } else {
if ('sortable' === getDragAction(e)) {
e.preventDefault(); e.preventDefault();
let node = (e.target.closest ? e.target : e.target.parentNode).closest('[draggable]'); }
if (node && node !== dragData.data && parent.contains(node)) {
let rect = node.getBoundingClientRect(); }, false);
if (rect.top + (rect.height / 2) <= e.clientY) { element.addEventListener("dragend", () => dragData = null);
if (node.nextElementSibling !== dragData.data) { element.setAttribute('draggable', true);
node.after(dragData.data); }
} },
} else if (node.previousElementSibling !== dragData.data) {
node.before(dragData.data); // Drop selected messages on folder
dropmessages: {
init: (element, fValueAccessor) => {
const folder = fValueAccessor(),
// folder = ko.dataFor(element),
fnStop = e => {
e.preventDefault();
element.classList.remove('droppableHover');
dragTimer.stop();
},
fnHover = e => {
if ('messages' === getDragAction(e)) {
fnStop(e);
element.classList.add('droppableHover');
if (folder && folder.collapsed()) {
dragTimer.start(() => {
folder.collapsed(false);
setExpandedFolder(folder.fullName, true);
}, 500);
}
}
};
addEventsListeners(element, {
dragenter: fnHover,
dragover: fnHover,
dragleave: fnStop,
drop: e => {
fnStop(e);
if ('messages' === getDragAction(e) && ['move','copy'].includes(e.dataTransfer.effectAllowed)) {
let data = dragData.data;
if (folder && data && data.folder && isArray(data.uids)) {
moveMessagesToFolder(data.folder, data.uids, folder.fullName, data.copy && e.ctrlKey);
} }
} }
} }
};
element.addEventListener("dragstart", e => {
dragData = {
action: 'sortable',
element: element
};
setDragAction(e, 'sortable', 'move', element, element);
element.style.opacity = 0.25;
});
element.addEventListener("dragend", e => {
element.style.opacity = null;
if ('sortable' === getDragAction(e)) {
dragData.data.style.cssText = '';
let row = parent.rows[options.list.indexOf(ko.dataFor(element))];
if (row != dragData.data) {
row.before(dragData.data);
}
dragData = null;
}
});
if (!parent.sortable) {
parent.sortable = true;
parent.addEventListener("dragenter", fnHover);
parent.addEventListener("dragover", fnHover);
parent.addEventListener("drop", e => {
if ('sortable' === getDragAction(e)) {
e.preventDefault();
let data = ko.dataFor(dragData.data),
from = options.list.indexOf(data),
to = [...parent.children].indexOf(dragData.data);
if (from != to) {
let arr = options.list();
arr.splice(to, 0, ...arr.splice(from, 1));
options.list(arr);
}
dragData = null;
options.afterMove && options.afterMove();
}
}); });
} }
} },
};
ko.bindingHandlers.initDom = { sortableItem: {
init: (element, fValueAccessor) => fValueAccessor()(element) init: (element, fValueAccessor) => {
}; let options = ko.unwrap(fValueAccessor()) || {},
parent = element.parentNode,
ko.bindingHandlers.onEsc = { fnHover = e => {
init: (element, fValueAccessor, fAllBindings, viewModel) => { if ('sortable' === getDragAction(e)) {
let fn = event => { e.preventDefault();
if ('Escape' == event.key) { let node = (e.target.closest ? e.target : e.target.parentNode).closest('[draggable]');
element.dispatchEvent(new Event('change')); if (node && node !== dragData.data && parent.contains(node)) {
fValueAccessor().call(viewModel); let rect = node.getBoundingClientRect();
if (rect.top + (rect.height / 2) <= e.clientY) {
if (node.nextElementSibling !== dragData.data) {
node.after(dragData.data);
}
} else if (node.previousElementSibling !== dragData.data) {
node.before(dragData.data);
}
}
}
};
addEventsListeners(element, {
dragstart: e => {
dragData = {
action: 'sortable',
element: element
};
setDragAction(e, 'sortable', 'move', element, element);
element.style.opacity = 0.25;
},
dragend: e => {
element.style.opacity = null;
if ('sortable' === getDragAction(e)) {
dragData.data.style.cssText = '';
let row = parent.rows[options.list.indexOf(ko.dataFor(element))];
if (row != dragData.data) {
row.before(dragData.data);
}
dragData = null;
}
}
});
if (!parent.sortable) {
parent.sortable = true;
addEventsListeners(parent, {
dragenter: fnHover,
dragover: fnHover,
drop: e => {
if ('sortable' === getDragAction(e)) {
e.preventDefault();
let data = ko.dataFor(dragData.data),
from = options.list.indexOf(data),
to = [...parent.children].indexOf(dragData.data);
if (from != to) {
let arr = options.list();
arr.splice(to, 0, ...arr.splice(from, 1));
options.list(arr);
}
dragData = null;
options.afterMove && options.afterMove();
}
}
});
} }
}; }
element.addEventListener('keyup', fn); },
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
}
};
ko.bindingHandlers.registerBootstrapDropdown = { initDom: {
init: element => { init: (element, fValueAccessor) => fValueAccessor()(element)
rl.Dropdowns.register(element); },
element.ddBtn = new BSN.Dropdown(element.querySelector('.dropdown-toggle'));
}
};
ko.bindingHandlers.openDropdownTrigger = { onEsc: {
update: (element, fValueAccessor) => { init: (element, fValueAccessor, fAllBindings, viewModel) => {
if (ko.unwrap(fValueAccessor())) { let fn = event => {
const el = element.ddBtn; if ('Escape' == event.key) {
el.open || el.toggle(); element.dispatchEvent(new Event('change'));
// el.focus(); fValueAccessor().call(viewModel);
}
};
element.addEventListener('keyup', fn);
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
}
},
rl.Dropdowns.detectVisibility(); registerBootstrapDropdown: {
fValueAccessor()(false); init: element => {
rl.Dropdowns.register(element);
element.ddBtn = new BSN.Dropdown(element.querySelector('.dropdown-toggle'));
}
},
openDropdownTrigger: {
update: (element, fValueAccessor) => {
if (ko.unwrap(fValueAccessor())) {
const el = element.ddBtn;
el.open || el.toggle();
// el.focus();
rl.Dropdowns.detectVisibility();
fValueAccessor()(false);
}
} }
} }
}; });

202
dev/External/ko.js vendored
View file

@ -5,6 +5,10 @@ import { SaveSettingsStep } from 'Common/Enums';
import { arrayLength, isFunction, forEachObjectEntry } from 'Common/Utils'; import { arrayLength, isFunction, forEachObjectEntry } from 'Common/Utils';
export const export const
errorTip = (element, value) => value
? setTimeout(() => element.setAttribute('data-rainloopErrorTip', value), 100)
: element.removeAttribute('data-rainloopErrorTip'),
/** /**
* The value of the pureComputed observable shouldnt vary based on the * The value of the pureComputed observable shouldnt vary based on the
* number of evaluations or other hidden information. Its value should be * number of evaluations or other hidden information. Its value should be
@ -35,118 +39,116 @@ export const
return data; return data;
}; };
ko.bindingHandlers.tooltipErrorTip = { Object.assign(ko.bindingHandlers, {
init: (element, fValueAccessor) => { tooltipErrorTip: {
doc.addEventListener('click', () => { init: (element, fValueAccessor) => {
let value = fValueAccessor(); doc.addEventListener('click', () => {
ko.isObservable(value) && !ko.isComputed(value) && value(''); let value = fValueAccessor();
element.removeAttribute('data-rainloopErrorTip'); ko.isObservable(value) && !ko.isComputed(value) && value('');
}); errorTip(element);
}, });
update: (element, fValueAccessor) => { },
let value = ko.unwrap(fValueAccessor()); update: (element, fValueAccessor) => {
value = isFunction(value) ? value() : value; let value = ko.unwrap(fValueAccessor());
if (value) { value = isFunction(value) ? value() : value;
setTimeout(() => element.setAttribute('data-rainloopErrorTip', value), 100); errorTip(element, value);
} else {
element.removeAttribute('data-rainloopErrorTip');
} }
} },
};
ko.bindingHandlers.onEnter = { onEnter: {
init: (element, fValueAccessor, fAllBindings, viewModel) => { init: (element, fValueAccessor, fAllBindings, viewModel) => {
let fn = event => { let fn = event => {
if ('Enter' == event.key) { if ('Enter' == event.key) {
element.dispatchEvent(new Event('change')); element.dispatchEvent(new Event('change'));
fValueAccessor().call(viewModel); fValueAccessor().call(viewModel);
}
};
element.addEventListener('keydown', fn);
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keydown', fn));
}
},
onSpace: {
init: (element, fValueAccessor, fAllBindings, viewModel) => {
let fn = event => {
if (' ' == event.key) {
fValueAccessor().call(viewModel, event);
}
};
element.addEventListener('keyup', fn);
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
}
},
i18nInit: {
init: element => i18nToNodes(element)
},
i18nUpdate: {
update: (element, fValueAccessor) => {
ko.unwrap(fValueAccessor());
i18nToNodes(element);
}
},
title: {
update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor())
},
command: {
init: (element, fValueAccessor, fAllBindings, viewModel, bindingContext) => {
const command = fValueAccessor();
if (!command || !command.enabled || !command.canExecute) {
throw new Error('Value should be a command');
} }
};
element.addEventListener('keydown', fn);
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keydown', fn));
}
};
ko.bindingHandlers.onSpace = { ko.bindingHandlers['FORM'==element.nodeName ? 'submit' : 'click'].init(
init: (element, fValueAccessor, fAllBindings, viewModel) => { element,
let fn = event => { fValueAccessor,
if (' ' == event.key) { fAllBindings,
fValueAccessor().call(viewModel, event); viewModel,
bindingContext
);
},
update: (element, fValueAccessor) => {
const cl = element.classList,
command = fValueAccessor();
let disabled = !command.enabled();
disabled = disabled || !command.canExecute();
cl.toggle('disabled', disabled);
if (element.matches('INPUT,TEXTAREA,BUTTON')) {
element.disabled = disabled;
} }
};
element.addEventListener('keyup', fn);
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
}
};
ko.bindingHandlers.i18nInit = {
init: element => i18nToNodes(element)
};
ko.bindingHandlers.i18nUpdate = {
update: (element, fValueAccessor) => {
ko.unwrap(fValueAccessor());
i18nToNodes(element);
}
};
ko.bindingHandlers.title = {
update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor())
};
ko.bindingHandlers.command = {
init: (element, fValueAccessor, fAllBindings, viewModel, bindingContext) => {
const command = fValueAccessor();
if (!command || !command.enabled || !command.canExecute) {
throw new Error('Value should be a command');
} }
ko.bindingHandlers['FORM'==element.nodeName ? 'submit' : 'click'].init(
element,
fValueAccessor,
fAllBindings,
viewModel,
bindingContext
);
}, },
update: (element, fValueAccessor) => {
const cl = element.classList,
command = fValueAccessor();
let disabled = !command.enabled(); saveTrigger: {
init: (element) => {
disabled = disabled || !command.canExecute(); let icon = element;
cl.toggle('disabled', disabled); if (element.matches('input,select,textarea')) {
element.classList.add('settings-saved-trigger-input');
if (element.matches('INPUT,TEXTAREA,BUTTON')) { element.after(element.saveTriggerIcon = icon = createElement('span'));
element.disabled = disabled; }
} icon.classList.add('settings-save-trigger');
} },
}; update: (element, fValueAccessor) => {
const value = parseInt(ko.unwrap(fValueAccessor()),10);
ko.bindingHandlers.saveTrigger = { let cl = (element.saveTriggerIcon || element).classList;
init: (element) => { if (element.saveTriggerIcon) {
let icon = element; cl.toggle('saving', value === SaveSettingsStep.Animate);
if (element.matches('input,select,textarea')) { cl.toggle('success', value === SaveSettingsStep.TrueResult);
element.classList.add('settings-saved-trigger-input'); cl.toggle('error', value === SaveSettingsStep.FalseResult);
element.after(element.saveTriggerIcon = icon = createElement('span')); }
} cl = element.classList;
icon.classList.add('settings-save-trigger');
},
update: (element, fValueAccessor) => {
const value = parseInt(ko.unwrap(fValueAccessor()),10);
let cl = (element.saveTriggerIcon || element).classList;
if (element.saveTriggerIcon) {
cl.toggle('saving', value === SaveSettingsStep.Animate);
cl.toggle('success', value === SaveSettingsStep.TrueResult); cl.toggle('success', value === SaveSettingsStep.TrueResult);
cl.toggle('error', value === SaveSettingsStep.FalseResult); cl.toggle('error', value === SaveSettingsStep.FalseResult);
} }
cl = element.classList;
cl.toggle('success', value === SaveSettingsStep.TrueResult);
cl.toggle('error', value === SaveSettingsStep.FalseResult);
} }
}; });
// extenders // extenders

View file

@ -1,4 +1,3 @@
import { Scope } from 'Common/Enums';
import { i18n } from 'Common/Translator'; import { i18n } from 'Common/Translator';
import { isFunction } from 'Common/Utils'; import { isFunction } from 'Common/Utils';
@ -58,8 +57,8 @@ class AskPopupView extends AbstractViewPopup {
} }
onBuild() { onBuild() {
// shortcuts.add('tab', 'shift', Scope.Ask, () => { // shortcuts.add('tab', 'shift', 'Ask', () => {
shortcuts.add('tab,arrowright,arrowleft', '', Scope.Ask, () => { shortcuts.add('tab,arrowright,arrowleft', '', 'Ask', () => {
let btn = this.querySelector('.buttonYes'); let btn = this.querySelector('.buttonYes');
if (btn.matches(':focus')) { if (btn.matches(':focus')) {
btn = this.querySelector('.buttonNo'); btn = this.querySelector('.buttonNo');
@ -68,7 +67,7 @@ class AskPopupView extends AbstractViewPopup {
return false; return false;
}); });
shortcuts.add('escape', '', Scope.Ask, () => { shortcuts.add('escape', '', 'Ask', () => {
this.noClick(); this.noClick();
return false; return false;
}); });

View file

@ -1,7 +1,6 @@
import ko from 'ko'; import ko from 'ko';
import { import {
Scope,
Notification, Notification,
UploadErrorCode UploadErrorCode
} from 'Common/Enums'; } from 'Common/Enums';
@ -50,6 +49,8 @@ import { ContactsPopupView } from 'View/Popup/Contacts';
import { ThemeStore } from 'Stores/Theme'; import { ThemeStore } from 'Stores/Theme';
const const
ScopeCompose = 'Compose',
base64_encode = text => btoa(text).match(/.{1,76}/g).join('\r\n'), base64_encode = text => btoa(text).match(/.{1,76}/g).join('\r\n'),
email = new EmailModel(), email = new EmailModel(),
@ -1323,42 +1324,42 @@ class ComposePopupView extends AbstractViewPopup {
this.addAttachmentEnabled(true); this.addAttachmentEnabled(true);
shortcuts.add('q', 'meta', Scope.Compose, ()=>false); shortcuts.add('q', 'meta', ScopeCompose, ()=>false);
shortcuts.add('w', 'meta', Scope.Compose, ()=>false); shortcuts.add('w', 'meta', ScopeCompose, ()=>false);
shortcuts.add('contextmenu', '', Scope.Compose, e => this.popupMenu(e)); shortcuts.add('contextmenu', '', ScopeCompose, e => this.popupMenu(e));
shortcuts.add('m', 'meta', Scope.Compose, e => this.popupMenu(e)); shortcuts.add('m', 'meta', ScopeCompose, e => this.popupMenu(e));
shortcuts.add('escape,close', '', Scope.Compose, () => { shortcuts.add('escape,close', '', ScopeCompose, () => {
this.skipCommand(); this.skipCommand();
return false; return false;
}); });
shortcuts.add('arrowdown', 'meta', Scope.Compose, () => { shortcuts.add('arrowdown', 'meta', ScopeCompose, () => {
this.skipCommand(); this.skipCommand();
return false; return false;
}); });
shortcuts.add('s', 'meta', Scope.Compose, () => { shortcuts.add('s', 'meta', ScopeCompose, () => {
this.saveCommand(); this.saveCommand();
return false; return false;
}); });
shortcuts.add('save', '', Scope.Compose, () => { shortcuts.add('save', '', ScopeCompose, () => {
this.saveCommand(); this.saveCommand();
return false; return false;
}); });
if (Settings.app('allowCtrlEnterOnCompose')) { if (Settings.app('allowCtrlEnterOnCompose')) {
shortcuts.add('enter', 'meta', Scope.Compose, () => { shortcuts.add('enter', 'meta', ScopeCompose, () => {
this.sendCommand(); this.sendCommand();
return false; return false;
}); });
} }
shortcuts.add('mailsend', '', Scope.Compose, () => { shortcuts.add('mailsend', '', ScopeCompose, () => {
this.sendCommand(); this.sendCommand();
return false; return false;
}); });
shortcuts.add('escape,close', 'shift', Scope.Compose, () => { shortcuts.add('escape,close', 'shift', ScopeCompose, () => {
this.modalVisibility() && this.tryToClosePopup(); this.modalVisibility() && this.tryToClosePopup();
return false; return false;
}); });

View file

@ -1,9 +1,6 @@
import { koArrayWithDestroy } from 'External/ko'; import { koArrayWithDestroy } from 'External/ko';
import { import { SaveSettingsStep } from 'Common/Enums';
SaveSettingsStep,
Scope
} from 'Common/Enums';
import { ComposeType } from 'Common/EnumsUser'; import { ComposeType } from 'Common/EnumsUser';
@ -26,7 +23,9 @@ import { ContactPropertyModel, ContactPropertyType } from 'Model/ContactProperty
import { decorateKoCommands } from 'Knoin/Knoin'; import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { AbstractViewPopup } from 'Knoin/AbstractViews';
const CONTACTS_PER_PAGE = 50, const
CONTACTS_PER_PAGE = 50,
ScopeContacts = 'Contacts',
propertyIsMail = prop => prop.isType(ContactPropertyType.Email), propertyIsMail = prop => prop.isType(ContactPropertyType.Email),
propertyIsName = prop => prop.isType(ContactPropertyType.FirstName) || prop.isType(ContactPropertyType.LastName); propertyIsName = prop => prop.isType(ContactPropertyType.FirstName) || prop.isType(ContactPropertyType.LastName);
@ -446,14 +445,14 @@ class ContactsPopupView extends AbstractViewPopup {
} }
onBuild(dom) { onBuild(dom) {
this.selector.init(dom.querySelector('.b-list-content'), Scope.Contacts); this.selector.init(dom.querySelector('.b-list-content'), ScopeContacts);
shortcuts.add('delete', '', Scope.Contacts, () => { shortcuts.add('delete', '', ScopeContacts, () => {
this.deleteCommand(); this.deleteCommand();
return false; return false;
}); });
shortcuts.add('c,w', '', Scope.Contacts, () => { shortcuts.add('c,w', '', ScopeContacts, () => {
this.newMessageCommand(); this.newMessageCommand();
return false; return false;
}); });

View file

@ -1,5 +1,3 @@
import { Scope } from 'Common/Enums';
import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { AbstractViewPopup } from 'Knoin/AbstractViews';
export class KeyboardShortcutsHelpPopupView extends AbstractViewPopup { export class KeyboardShortcutsHelpPopupView extends AbstractViewPopup {
@ -14,7 +12,7 @@ export class KeyboardShortcutsHelpPopupView extends AbstractViewPopup {
// shortcuts.add('tab', 'shift', // shortcuts.add('tab', 'shift',
shortcuts.add('tab,arrowleft,arrowright', '', shortcuts.add('tab,arrowleft,arrowright', '',
Scope.KeyboardShortcutsHelp, 'KeyboardShortcutsHelp',
event => { event => {
let next = 0; let next = 0;
tabs.forEach((node, index) => { tabs.forEach((node, index) => {

View file

@ -1,4 +1,3 @@
import { Scope } from 'Common/Enums';
import { doc } from 'Common/Globals'; import { doc } from 'Common/Globals';
import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { AbstractViewPopup } from 'Knoin/AbstractViews';
@ -119,7 +118,7 @@ class OpenPgpKeyPopupView extends AbstractViewPopup {
} }
onBuild() { onBuild() {
shortcuts.add('a', 'meta', Scope.OpenPgpKey, () => { shortcuts.add('a', 'meta', 'OpenPgpKey', () => {
this.selectKey(); this.selectKey();
return false; return false;
}); });

View file

@ -13,7 +13,7 @@ import {
import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { doc, leftPanelDisabled, moveAction, Settings, SettingsCapa, SettingsGet, fireEvent } from 'Common/Globals'; import { doc, leftPanelDisabled, moveAction, Settings, SettingsCapa, SettingsGet, fireEvent, addEventsListeners } from 'Common/Globals';
import { computedPaginatorHelper, showMessageComposer, populateMessageBody } from 'Common/UtilsUser'; import { computedPaginatorHelper, showMessageComposer, populateMessageBody } from 'Common/UtilsUser';
import { FileInfo } from 'Common/File'; import { FileInfo } from 'Common/File';
@ -642,28 +642,29 @@ export class MailMessageList extends AbstractViewRight {
this.selector.init(dom.querySelector('.b-content'), Scope.MessageList); this.selector.init(dom.querySelector('.b-content'), Scope.MessageList);
dom.addEventListener('click', event => { addEventsListeners(dom, {
ThemeStore.isMobile() && !eqs(event, '.toggleLeft') && leftPanelDisabled(true); click: event => {
ThemeStore.isMobile() && !eqs(event, '.toggleLeft') && leftPanelDisabled(true);
if (eqs(event, '.messageList') && Scope.MessageView === AppUserStore.focusedState()) { if (eqs(event, '.messageList') && Scope.MessageView === AppUserStore.focusedState()) {
AppUserStore.focusedState(Scope.MessageList); AppUserStore.focusedState(Scope.MessageList);
}
let el = eqs(event, '.e-paginator a');
el && this.gotoPage(ko.dataFor(el));
eqs(event, '.checkboxCheckAll') && this.checkAll(!this.checkAll());
el = eqs(event, '.flagParent');
el && this.flagMessages(ko.dataFor(el));
el = eqs(event, '.threads-len');
el && this.gotoThread(ko.dataFor(el));
},
dblclick: event => {
let el = eqs(event, '.actionHandle');
el && this.gotoThread(ko.dataFor(el));
} }
let el = eqs(event, '.e-paginator a');
el && this.gotoPage(ko.dataFor(el));
eqs(event, '.checkboxCheckAll') && this.checkAll(!this.checkAll());
el = eqs(event, '.flagParent');
el && this.flagMessages(ko.dataFor(el));
el = eqs(event, '.threads-len');
el && this.gotoThread(ko.dataFor(el));
});
dom.addEventListener('dblclick', event => {
let el = eqs(event, '.actionHandle');
el && this.gotoThread(ko.dataFor(el));
}); });
// initUploaderForAppend // initUploaderForAppend