There this works
This commit is contained in:
djmaze 2020-09-26 10:20:24 +02:00
parent 3a48cc8b7f
commit c7054ff56c
19 changed files with 32 additions and 441 deletions

View file

@ -6,7 +6,7 @@
<h1>SnappyMail</h1>
<br>
<p>
Simple, modern &amp; fast web-based email client.
Simple, modern, lightweight &amp; fast web-based email client.
</p>
<p>
The drastically upgraded &amp; secured fork of <a href="https://github.com/RainLoop/rainloop-webmail">RainLoop Webmail Community edition</a>.
@ -85,6 +85,7 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
* Replaced knockout-sortable with native HTML5 drag&drop
* Replaced simplestatemanager with @media
* Replaced inputosaurus to native
* Replaced keymaster with own shortcuts handler
* Removed pikaday
* Removed underscore
* Removed polyfills
@ -102,22 +103,22 @@ 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 | 850.202 |
|app.js |4.184.455 |2.488.837 |
|admin.js |2.130.942 | 844.753 |
|app.js |4.184.455 |2.487.401 |
|boot.js | 671.522 | 5.285 |
|libs.js | 647.614 | 250.948 |
|libs.js | 647.614 | 246.085 |
|polyfills.js | 325.834 | 0 |
|TOTAL |7.960.367 |3.595.272 |
|TOTAL |7.960.367 |3.583.524 |
|js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|--------------- |--------: |--------: |--------: |--------: |--------: |
|admin.min.js | 252.147 | 116.626 | 73.657 | 33.343 | 28.869 |
|app.min.js | 511.202 | 339.176 |140.462 | 89.271 | 72.393 |
|admin.min.js | 252.147 | 116.068 | 73.657 | 33.279 | 28.711 |
|app.min.js | 511.202 | 339.263 |140.462 | 89.051 | 72.194 |
|boot.min.js | 66.007 | 2.935 | 22.567 | 1.510 | 1.285 |
|libs.min.js | 572.545 | 148.303 |176.720 | 52.206 | 46.472 |
|libs.min.js | 572.545 | 147.019 |176.720 | 51.583 | 45.825 |
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|TOTAL |1.434.353 | 607.040 |424.718 |176.330 |149.019 |
|TOTAL (no admin) |1.182.206 | 490.414 |351.061 |142.987 |120.150 |
|TOTAL |1.434.353 | 605.285 |424.718 |175.423 |148.015 |
|TOTAL (no admin) |1.182.206 | 489.217 |351.061 |142.144 |119.304 |
For a user its around 58% smaller and faster than traditional RainLoop.
@ -144,8 +145,8 @@ For a user its around 58% smaller and faster than traditional RainLoop.
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|-------------- |-------: |-------: |------: |------: |------: |
|app.css | 340.334 | 190.855 | 46,959 | 29.156 | 24.561 |
|app.min.css | 274.791 | 157.619 | 39.618 | 26.336 | 22.667 |
|app.css | 340.334 | 190.086 | 46,959 | 29.415 | 24.717 |
|app.min.css | 274.791 | 156.076 | 39.618 | 26.034 | 22.418 |
|boot.css | | 2.534 | | 837 | 668 |
|boot.min.css | | 2.055 | | 732 | 560 |

View file

@ -42,7 +42,7 @@ class AbstractApp {
$doc.addEventListener('keypress', fn);
$doc.addEventListener('click', fn);
shortcuts.add(['escape','enter'], '', KeyState.All, () => rl.Dropdowns.detectVisibility());
shortcuts.add('escape,enter', '', KeyState.All, () => rl.Dropdowns.detectVisibility());
}
remote() {

View file

@ -286,11 +286,11 @@ class Selector {
shortcuts.add('arrowup', 'meta', keyScope, () => false);
shortcuts.add('arrowdown', 'meta', keyScope, () => false);
shortcuts.add(['arrowup','arrowdown'], 'shift', keyScope, event => {
shortcuts.add('arrowup,arrowdown', 'shift', keyScope, event => {
this.newSelectPosition(event.key, true);
return false;
});
shortcuts.add(['arrowup','arrowdown','home','end','pageup','pagedown','insert','space'], '', keyScope, event => {
shortcuts.add('arrowup,arrowdown,home,end,pageup,pagedown,insert,space', '', keyScope, event => {
this.newSelectPosition(event.key, false);
return false;
});

View file

@ -26,7 +26,7 @@ class MenuSettingsAdminView extends AbstractViewNext {
}
onBuild(dom) {
shortcuts.add(['arrowup','arrowdown'], '', KeyState.Settings,
shortcuts.add('arrowup,arrowdown', '', KeyState.Settings,
settingsMenuKeysHandler(dom.querySelectorAll('.b-admin-menu .e-item')));
}
}

View file

@ -87,7 +87,7 @@ class AskPopupView extends AbstractViewNext {
onBuild() {
// shortcuts.add('tab', 'shift', KeyState.PopupAsk, () => {
shortcuts.add(['tab','arrowright','arrowleft'], '', KeyState.PopupAsk, () => {
shortcuts.add('tab,arrowright,arrowleft', '', KeyState.PopupAsk, () => {
let btn = this.querySelector('.buttonYes');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonNo');

View file

@ -303,7 +303,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext {
}
onBuild() {
// shortcuts.add(('tab', 'shift', KeyState.PopupComposeOpenPGP, () => {
// shortcuts.add('tab', 'shift', KeyState.PopupComposeOpenPGP, () => {
shortcuts.add('tab', '', KeyState.PopupComposeOpenPGP, () => {
let btn = this.querySelector('.inputPassword');
if (btn.matches(':focus')) {

View file

@ -611,7 +611,7 @@ class ContactsPopupView extends AbstractViewNext {
return false;
});
shortcuts.add(['c','w'], '', KeyState.ContactList, () => {
shortcuts.add('c,w', '', KeyState.ContactList, () => {
this.newMessageCommand();
return false;
});

View file

@ -17,7 +17,7 @@ class KeyboardShortcutsHelpPopupView extends AbstractViewNext {
dom.querySelectorAll('a[data-toggle="tab"]').forEach(node => node.Tab || new BSN.Tab(node));
// shortcuts.add('tab', 'shift',
shortcuts.add(['tab','arrowleft','arrowright'], '',
shortcuts.add('tab,arrowleft,arrowright', '',
KeyState.PopupKeyboardShortcutsHelp,
((event, handler)=>{
if (event && handler) {

View file

@ -79,7 +79,7 @@ class MessageOpenPgpPopupView extends AbstractViewNext {
}
onBuild(oDom) {
// shortcuts.add('tab','shift', KeyState.PopupMessageOpenPGP, () => {
// shortcuts.add('tab', 'shift', KeyState.PopupMessageOpenPGP, () => {
shortcuts.add('tab', '', KeyState.PopupMessageOpenPGP, () => {
let btn = this.querySelector('.inputPassword');
if (btn.matches(':focus')) {

View file

@ -121,7 +121,7 @@ class FolderListMailBoxUserView extends AbstractViewNext {
el && fSelectFolder(el, event, false);
});
shortcuts.add(['arrowup','arrowdown'], '', KeyState.FolderList, event => {
shortcuts.add('arrowup,arrowdown', '', KeyState.FolderList, event => {
let items = [], index = 0;
dom.querySelectorAll('.b-folders .e-item .e-link:not(.hidden)').forEach(node => {
if (node.offsetHeight || node.getClientRects().length) {
@ -168,7 +168,7 @@ class FolderListMailBoxUserView extends AbstractViewNext {
});
// shortcuts.add('tab', 'shift', KeyState.FolderList, () => {
shortcuts.add(['escape','tab','arrowright'], '', KeyState.FolderList, () => {
shortcuts.add('escape,tab,arrowright', '', KeyState.FolderList, () => {
AppStore.focusedState(Focused.MessageList);
moveAction(false);
return false;

View file

@ -786,7 +786,7 @@ class MessageListMailBoxUserView extends AbstractViewNext {
if (Settings.capa(Capa.Composer)) {
// write/compose (open compose popup)
shortcuts.add(['w','c'], '', [KeyState.MessageList, KeyState.MessageView], () => {
shortcuts.add('w,c', '', [KeyState.MessageList, KeyState.MessageView], () => {
showScreenPopup(require('View/Popup/Compose'));
return false;
});
@ -877,7 +877,7 @@ class MessageListMailBoxUserView extends AbstractViewNext {
AppStore.focusedState(Focused.FolderList);
return false;
});
shortcuts.add(['tab','arrowright'], '', KeyState.MessageList, () => {
shortcuts.add('tab,arrowright', '', KeyState.MessageList, () => {
this.message() && AppStore.focusedState(Focused.MessageView);
return false;
});

View file

@ -547,7 +547,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
initShortcuts() {
// exit fullscreen, back
shortcuts.add(['escape','backspace'], '', KeyState.MessageView, this.escShortcuts.bind(this));
shortcuts.add('escape,backspace', '', KeyState.MessageView, this.escShortcuts.bind(this));
// fullscreen
shortcuts.add('enter', '', KeyState.MessageView, () => {
@ -602,12 +602,12 @@ class MessageViewMailBoxUserView extends AbstractViewNext {
return true;
});
shortcuts.add(['arrowup','arrowleft'], 'meta', [KeyState.MessageList, KeyState.MessageView], () => {
shortcuts.add('arrowup,arrowleft', 'meta', [KeyState.MessageList, KeyState.MessageView], () => {
this.goUpCommand();
return false;
});
shortcuts.add(['arrowdown','arrowright'], 'meta', [KeyState.MessageList, KeyState.MessageView], () => {
shortcuts.add('arrowdown,arrowright', 'meta', [KeyState.MessageList, KeyState.MessageView], () => {
this.goDownCommand();
return false;
});

View file

@ -32,7 +32,7 @@ class MenuSettingsUserView extends AbstractViewNext {
);
}
shortcuts.add(['ArrowUp','ArrowDown'], '', KeyState.Settings,
shortcuts.add('arrowup,arrowdown', '', KeyState.Settings,
settingsMenuKeysHandler(dom.querySelectorAll('.b-settings-menu .e-item')));
}

View file

@ -6,7 +6,7 @@ const
doc = document,
meta = /Mac OS X/.test(navigator.userAgent) ? 'meta' : 'ctrl',
_scopes = {},
toArray = v => Array.isArray(v) ? v : [v],
toArray = v => Array.isArray(v) ? v : v.split(/\s*,\s*/),
fire = (actions, event) => {
let modifiers = [];
@ -20,7 +20,7 @@ const
actions[modifiers].forEach(cmd => {
try {
// call the handler and stop the event if neccessary
if (!event.defaultPrevented && cmd(event) === false) {
if (!event.defaultPrevented && cmd(event) === false) {
event.preventDefault();
event.stopPropagation();
}

View file

View file

@ -1,20 +0,0 @@
Copyright (c) 2011-2013 Thomas Fuchs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,212 +0,0 @@
# keymaster.js
Keymaster is a simple micro-library for defining and
dispatching keyboard shortcuts in web applications.
It has no dependencies.
*Its a work in progress (e.g. beta), so spare me your nerdrage and instead
contribute! Patches are welcome, but they are not guaranteed to make
it in.*
## Usage
Include `keymaster.js` in your web app*, by loading it as usual:
```html
<script src="keymaster.js"></script>
```
Keymaster has no dependencies and can be used completely standalone.
It should not interfere with any JavaScript libraries or frameworks.
_*Preferably use a minified version that fits your workflow. You can
run `make` to have UglifyJS (if you have it installed) create a
`keymaster.min.js` file for you._
## Defining shortcuts
One global method is exposed, `key` which defines shortcuts when
called directly.
```javascript
// define short of 'a'
key('a', function(){ alert('you pressed a!') });
// returning false stops the event and prevents default browser events
key('ctrl+r', function(){ alert('stopped reload!'); return false });
// multiple shortcuts that do the same thing
key('⌘+r, ctrl+r', function(){ });
```
The handler method is called with two arguments set, the keydown `event` fired, and
an object containing, among others, the following two properties:
`shortcut`: a string that contains the shortcut used, e.g. `ctrl+r`
`scope`: a string describing the scope (or `all`)
```javascript
key('⌘+r, ctrl+r', function(event, handler){
console.log(handler.shortcut, handler.scope);
});
// "ctrl+r", "all"
```
## Supported keys
Keymaster understands the following modifiers:
`⇧`, `shift`, `option`, `⌥`, `alt`, `ctrl`, `control`, `command`, and `⌘`.
The following special keys can be used for shortcuts:
`backspace`, `tab`, `clear`, `enter`, `return`, `esc`, `escape`, `space`,
`up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `del`, `delete`
and `f1` through `f19`.
## Modifier key queries
At any point in time (even in code other than key shortcut handlers),
you can query the `key` object for the state of any keys. This
allows easy implementation of things like shift+click handlers. For example,
`key.shift` is `true` if the shift key is currently pressed.
```javascript
if(key.shift) alert('shift is pressed, OMGZ!');
```
## Other key queries
At any point in time (even in code other than key shortcut handlers),
you can query the `key` object for the state of any key. This
is very helpful for game development using a game loop. For example,
`key.isPressed(77)` is `true` if the M key is currently pressed.
```javascript
if(key.isPressed("M")) alert('M key is pressed, can ya believe it!?');
if(key.isPressed(77)) alert('M key is pressed, can ya believe it!?');
```
You can also get these as an array using...
```javascript
key.getPressedKeyCodes() // returns an array of key codes currently pressed
```
## Scopes
If you want to reuse the same shortcut for separate areas in your single page app,
Keymaster supports switching between scopes. Use the `key.setScope` method to set scope.
```javascript
// define shortcuts with a scope
key('o, enter', 'issues', function(){ /* do something */ });
key('o, enter', 'files', function(){ /* do something else */ });
// set the scope (only 'all' and 'issues' shortcuts will be honored)
key.setScope('issues'); // default scope is 'all'
```
## Filter key presses
By default, when an `INPUT`, `SELECT` or `TEXTAREA` element is focused, Keymaster doesn't process any shortcuts.
You can change this by overwriting `key.filter` with a new function. This function is called before
Keymaster processes shortcuts, with the keydown event as argument.
If your function returns false, then the no shortcuts will be processed.
Here's the default implementation for reference:
```javascript
function filter(event){
var tagName = (event.target || event.srcElement).tagName;
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
}
```
If you only want _some_ shortcuts to work while in an input element, you can change the scope in the
`key.filter` function. Here's an example implementation, setting the scope to either `'input'` or `'other'`.
Don't forget to return `true` so the any shortcuts get processed.
```javascript
key.filter = function(event){
var tagName = (event.target || event.srcElement).tagName;
key.setScope(/^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other');
return true;
}
```
However a more robust way to handle this is to use proper
focus and blur event handlers on your input element, and change scopes there as you see fit.
## noConflict mode
You can call ```key.noConflict``` to remove the ```key``` function from global scope and restore whatever ```key``` was defined to before Keymaster was loaded. Calling ```key.noConflict``` will return the Keymaster ```key``` function.
```javascript
var k = key.noConflict();
k('a', function() { /* ... */ });
key()
// --> TypeError: 'undefined' is not a function
```
## Unbinding shortcuts
Similar to defining shortcuts, they can be unbound using `key.unbind`.
```javascript
// unbind 'a' handler
key.unbind('a');
// unbind a key only for a single scope
// when no scope is specified it defaults to the current scope (key.getScope())
key.unbind('o, enter', 'issues');
key.unbind('o, enter', 'files');
```
## Notes
Keymaster should work with any browser that fires `keyup` and `keydown` events,
and is tested with IE (6+), Safari, Firefox and Chrome.
See [http://madrobby.github.com/keymaster/](http://madrobby.github.com/keymaster/) for a live demo.
## CoffeeScript
If you're using CoffeeScript, configuring key shortcuts couldn't be simpler:
```coffeescript
key 'a', -> alert('you pressed a!')
key '⌘+r, ctrl+r', ->
alert 'stopped reload!'
off
key 'o, enter', 'issues', ->
whatevs()
alert 'shift is pressed, OMGZ!' if key.shift
```
## Contributing
To contribute, please fork Keymaster, add your patch and tests for it (in the `test/` folder) and
submit a pull request.
## TODOs
* Finish test suite
Keymaster is (c) 2011-2013 Thomas Fuchs and may be freely distributed under the MIT license.
See the `MIT-LICENSE` file.

View file

@ -1,167 +0,0 @@
// keymaster.js
// (c) 2011-2013 Thomas Fuchs
// keymaster.js may be freely distributed under the MIT license.
(global=>{
var k,
_handlers = {},
_mods = { 16: false, 18: false, 17: false, 91: false },
_scope = 'all',
// modifier keys
_MODIFIERS = {
'⇧': 16, shift: 16,
'⌥': 18, alt: 18, option: 18,
'⌃': 17, ctrl: 17, control: 17,
'⌘': 91, command: 91
},
// special keys
_MAP = {
backspace: 8, tab: 9, clear: 12,
enter: 13, 'return': 13,
esc: 27, escape: 27, space: 32,
left: 37, up: 38,
right: 39, down: 40,
insert: 45,
del: 46, 'delete': 46,
home: 36, end: 35,
pageup: 33, pagedown: 34,
',': 188, '.': 190, '/': 191,
'`': 192, '-': 189, '=': 187,
';': 186, '\'': 222,
'[': 219, ']': 221, '\\': 220
},
code = x => _MAP[x] || x.toUpperCase().charCodeAt(0),
_downKeys = [];
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
var modifierMap = {
16:'shiftKey',
18:'altKey',
17:'ctrlKey',
91:'metaKey'
};
// parse and assign shortcut
function assignKey(key, scope, method){
key = key.replace(/\s/g, '');
var keys = key.split(','), mods;
if ((keys[keys.length - 1]) == '') {
keys[keys.length - 2] += ',';
}
if (method === undefined) {
method = scope;
scope = 'all';
}
// for each shortcut
for (var i = 0; i < keys.length; i++) {
// set modifier keys if any
mods = [];
key = keys[i].split('+');
if (key.length > 1){
mods = key.slice(0, key.length - 1);
mods.forEach((mod, mi) => mods[mi] = _MODIFIERS[mod]);
key = [key[key.length-1]];
}
// convert to keycode and...
key = code(key[0]);
// ...store handler
if (!(key in _handlers)) _handlers[key] = [];
if (typeof scope !== 'string' && scope.length && typeof scope[0] === 'string') {
scope.forEach(item => {
_handlers[key].push({ shortcut: keys[i], scope: item, method: method, key: keys[i], mods: mods });
});
} else {
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
}
}
}
// initialize key.<modifier> to false
for(k in _MODIFIERS) assignKey[k] = false;
const getScope = () => _scope || 'all';
// set the handlers globally on document
document.addEventListener('keydown', event => {
var key = event.keyCode, k, modifiersMatch, scope;
if (!_downKeys.includes(key)) {
_downKeys.push(key);
}
// if a modifier key, set the key.<modifierkeyname> property to true and return
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
if(key in _mods) {
_mods[key] = true;
// 'assignKey' from inside this closure is exported to window.key
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
return;
}
for(k in _mods) _mods[k] = event[modifierMap[k]];
// see if we need to ignore the keypress (filter() can can be overridden)
// by default ignore key presses if a select, textarea, or input is focused
if(!assignKey.filter.call(this, event)) return;
// abort if no potentially matching shortcuts found
if (!(key in _handlers)) return;
scope = getScope();
// for each potential shortcut
_handlers[key].forEach(handler => {
// see if it's in the current scope
if(handler.scope == scope || handler.scope == 'all'){
// check if modifiers match if any
modifiersMatch = handler.mods.length > 0;
for(k in _mods)
if((!_mods[k] && handler.mods.includes(+k)) ||
(_mods[k] && !handler.mods.includes(+k))) modifiersMatch = false;
// call the handler and stop the event if neccessary
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
if(handler.method(event, handler)===false){
event.preventDefault();
event.stopPropagation();
}
}
}
});
});
// unset modifier keys on keyup
document.addEventListener('keyup', event => {
var key = event.keyCode, k,
i = _downKeys.indexOf(key);
// remove key from _downKeys
if (i >= 0) {
_downKeys.splice(i, 1);
}
if(key == 93 || key == 224) key = 91;
if(key in _mods) {
_mods[key] = false;
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
}
});
// reset modifiers to false whenever the window is (re)focused.
addEventListener('focus', () => {
for(let k in _mods) _mods[k] = false;
for(let k in _MODIFIERS) assignKey[k] = false;
});
// set window.key and window.key.set/get, and the default filter
global.key = assignKey;
global.key.setScope = scope => { _scope = scope || 'all' };
global.key.getScope = getScope;
global.key.filter = event => {
var tagName = event.target.tagName;
// ignore keypressed in any elements that support keyboard data input
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
};
})(this);

View file

@ -1,11 +0,0 @@
{
"name": "keymaster",
"description": "library for defining and dispatching keyboard shortcuts",
"version": "1.6.2",
"author": "Thomas Fuchs <thomas@slash7.com> (http://mir.aculo.us)",
"repository": {
"type": "git",
"url": "https://github.com/madrobby/keymaster"
},
"main": "./keymaster.js"
}