mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 23:47:03 +03:00
Move some knockoutjs bindings, extenders and functions to Admin and User specific areas
This commit is contained in:
parent
95e88af017
commit
9c18126843
8 changed files with 368 additions and 344 deletions
19
README.md
19
README.md
|
|
@ -54,6 +54,7 @@ This fork has the following changes:
|
||||||
* Embed boot.js and boot.css into index.html
|
* Embed boot.js and boot.css into index.html
|
||||||
* Ongoing removal of old JavaScript code (things are native these days)
|
* Ongoing removal of old JavaScript code (things are native these days)
|
||||||
* Added modified [Squire](https://github.com/neilj/Squire) HTML editor as replacement for CKEditor
|
* Added modified [Squire](https://github.com/neilj/Squire) HTML editor as replacement for CKEditor
|
||||||
|
* Split Admin specific JavaScript code from User code
|
||||||
|
|
||||||
### Removal of old JavaScript
|
### Removal of old JavaScript
|
||||||
|
|
||||||
|
|
@ -89,23 +90,23 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
|
||||||
|
|
||||||
|js/* |1.14.0 |native |
|
|js/* |1.14.0 |native |
|
||||||
|----------- |--------: |--------: |
|
|----------- |--------: |--------: |
|
||||||
|admin.js |2.130.942 | 935.611 |
|
|admin.js |2.130.942 | 857.772 |
|
||||||
|app.js |4.184.455 |2.517.449 |
|
|app.js |4.184.455 |2.507.407 |
|
||||||
|boot.js | 671.522 | 5.777 |
|
|boot.js | 671.522 | 5.777 |
|
||||||
|libs.js | 647.614 | 327.257 |
|
|libs.js | 647.614 | 327.257 |
|
||||||
|polyfills.js | 325.834 | 0 |
|
|polyfills.js | 325.834 | 0 |
|
||||||
|TOTAL |7.960.367 |3.786.094 |
|
|TOTAL |7.960.367 |3.698.213 |
|
||||||
|
|
||||||
|js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
|js/min/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
||||||
|--------------- |--------: |--------: |--------: |--------: |--------: |
|
|--------------- |--------: |--------: |--------: |--------: |--------: |
|
||||||
|admin.min.js | 252.147 | 128.146 | 73.657 | 37.448 | 32.137 |
|
|admin.min.js | 252.147 | 117.678 | 73.657 | 33.668 | 29.096 |
|
||||||
|app.min.js | 511.202 | 344.299 |140.462 | 90.265 | 73.006 |
|
|app.min.js | 511.202 | 342.012 |140.462 | 89.893 | 72.807 |
|
||||||
|boot.min.js | 66.007 | 3.101 | 22.567 | 1.576 | 1.346 |
|
|boot.min.js | 66.007 | 3.101 | 22.567 | 1.576 | 1.346 |
|
||||||
|libs.min.js | 572.545 | 304.000 |176.720 | 94.870 | 83.524 |
|
|libs.min.js | 572.545 | 304.000 |176.720 | 94.870 | 83.524 |
|
||||||
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|
||||||
|TOTAL |1.434.353 | 779.546 |424.718 |224.159 |190.013 |
|
|TOTAL |1.434.353 | 766.791 |424.718 |220.007 |186.773 |
|
||||||
|
|
||||||
654.807 bytes (200.559 gzip) is not much, but it feels faster.
|
667.562 bytes (204.711 gzip) is not much, but it feels faster.
|
||||||
|
|
||||||
### CSS changes
|
### CSS changes
|
||||||
|
|
||||||
|
|
@ -130,8 +131,8 @@ Things might work in Edge 18, Firefox 50-62 and Chrome 54-68 due to one polyfill
|
||||||
|
|
||||||
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
|css/* |1.14.0 |native |gzip 1.14 |gzip |brotli |
|
||||||
|-------------- |-------: |-------: |------: |------: |------: |
|
|-------------- |-------: |-------: |------: |------: |------: |
|
||||||
|app.css | 340.334 | 253.691 | 46,959 | 36.574 | 30.777 |
|
|app.css | 340.334 | 254.033 | 46,959 | 36.648 | 30.815 |
|
||||||
|app.min.css | 274.791 | 207.298 | 39.618 | 32.001 | 27.155 |
|
|app.min.css | 274.791 | 207.558 | 39.618 | 32.050 | 27.234 |
|
||||||
|boot.css | | 2.538 | | 837 | 668 |
|
|boot.css | | 2.538 | | 837 | 668 |
|
||||||
|boot.min.css | | 2.055 | | 732 | 560 |
|
|boot.min.css | | 2.055 | | 732 | 560 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import ko from 'ko';
|
import ko from 'External/Admin/ko';
|
||||||
|
|
||||||
import { StorageResultType } from 'Common/Enums';
|
import { StorageResultType } from 'Common/Enums';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,10 @@ import { startScreens, showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
import { AbstractApp } from 'App/Abstract';
|
import { AbstractApp } from 'App/Abstract';
|
||||||
|
|
||||||
const doc = document, Settings = rl.settings;
|
require('External/User/ko');
|
||||||
|
|
||||||
|
const doc = document,
|
||||||
|
Settings = rl.settings;
|
||||||
|
|
||||||
class AppUser extends AbstractApp {
|
class AppUser extends AbstractApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
||||||
68
dev/External/Admin/ko.js
vendored
Normal file
68
dev/External/Admin/ko.js
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { SaveSettingsStep } from 'Common/Enums';
|
||||||
|
|
||||||
|
const ko = window.ko;
|
||||||
|
|
||||||
|
ko.bindingHandlers.saveTrigger = {
|
||||||
|
init: (element) => {
|
||||||
|
element.saveTriggerType = element.matches('input[type=text],input[type=email],input[type=password],select,textarea')
|
||||||
|
? 'input' : 'custom';
|
||||||
|
|
||||||
|
if ('custom' === element.saveTriggerType) {
|
||||||
|
element.append(
|
||||||
|
' ',
|
||||||
|
Element.fromHTML('<i class="icon-spinner animated"></i>'),
|
||||||
|
Element.fromHTML('<i class="icon-remove error"></i>'),
|
||||||
|
Element.fromHTML('<i class="icon-ok success"></i>')
|
||||||
|
);
|
||||||
|
element.classList.add('settings-saved-trigger');
|
||||||
|
} else {
|
||||||
|
element.classList.add('settings-saved-trigger-input');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: (element, fValueAccessor) => {
|
||||||
|
const value = parseInt(ko.unwrap(fValueAccessor()),10);
|
||||||
|
if ('custom' === element.saveTriggerType) {
|
||||||
|
element.querySelector('.animated').hidden = value !== SaveSettingsStep.Animate;
|
||||||
|
element.querySelector('.success').hidden = value !== SaveSettingsStep.TrueResult;
|
||||||
|
element.querySelector('.error').hidden = value !== SaveSettingsStep.FalseResult;
|
||||||
|
} else if (value !== SaveSettingsStep.Animate) {
|
||||||
|
element.classList.toggle('success', value === SaveSettingsStep.TrueResult);
|
||||||
|
element.classList.toggle('error', value === SaveSettingsStep.FalseResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.extenders.idleTrigger = (target) => {
|
||||||
|
target.trigger = ko.observable(SaveSettingsStep.Idle);
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.extenders.posInterer = (target, defaultVal) => {
|
||||||
|
const Utils = require('Common/Utils'),
|
||||||
|
result = ko.computed({
|
||||||
|
read: target,
|
||||||
|
write: (newValue) => {
|
||||||
|
let val = Utils.pInt(newValue.toString(), defaultVal);
|
||||||
|
if (0 >= val) {
|
||||||
|
val = defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === target() && '' + val !== '' + newValue) {
|
||||||
|
target(val + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
target(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result(target());
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// functions
|
||||||
|
|
||||||
|
ko.observable.fn.idleTrigger = function() {
|
||||||
|
return this.extend({ 'idleTrigger': true });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ko;
|
||||||
263
dev/External/User/ko.js
vendored
Normal file
263
dev/External/User/ko.js
vendored
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
import { bMobileDevice } from 'Common/Globals';
|
||||||
|
|
||||||
|
const ko = window.ko,
|
||||||
|
$ = jQuery;
|
||||||
|
|
||||||
|
ko.bindingHandlers.editor = {
|
||||||
|
init: (element, fValueAccessor) => {
|
||||||
|
let editor = null;
|
||||||
|
|
||||||
|
const fValue = fValueAccessor(),
|
||||||
|
HtmlEditor = require('Common/HtmlEditor').default,
|
||||||
|
fUpdateEditorValue = () => fValue && fValue.__editor && fValue.__editor.setHtmlOrPlain(fValue()),
|
||||||
|
fUpdateKoValue = () => fValue && fValue.__editor && fValue(fValue.__editor.getDataWithHtmlMark()),
|
||||||
|
fOnReady = () => {
|
||||||
|
fValue.__editor = editor;
|
||||||
|
fUpdateEditorValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ko.isObservable(fValue) && HtmlEditor) {
|
||||||
|
editor = new HtmlEditor(element, fUpdateKoValue, fOnReady, fUpdateKoValue);
|
||||||
|
|
||||||
|
fValue.__fetchEditorValue = fUpdateKoValue;
|
||||||
|
|
||||||
|
fValue.subscribe(fUpdateEditorValue);
|
||||||
|
|
||||||
|
// ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ttn = (element, fValueAccessor) => require('Common/Momentor').timeToNode(element, ko.unwrap(fValueAccessor()));
|
||||||
|
ko.bindingHandlers.moment = {
|
||||||
|
init: ttn,
|
||||||
|
update: ttn
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.emailsTags = {
|
||||||
|
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
||||||
|
const EmailModel = require('Model/Email').default,
|
||||||
|
$el = $(element),
|
||||||
|
fValue = fValueAccessor(),
|
||||||
|
fAllBindings = fAllBindingsAccessor(),
|
||||||
|
inputDelimiters = [',', ';', '\n'];
|
||||||
|
|
||||||
|
$el.inputosaurus({
|
||||||
|
parseOnBlur: true,
|
||||||
|
allowDragAndDrop: true,
|
||||||
|
focusCallback: value => {
|
||||||
|
if (fValue && fValue.focused) {
|
||||||
|
fValue.focused(!!value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputDelimiters: inputDelimiters,
|
||||||
|
autoCompleteSource: fAllBindings.autoCompleteSource || null,
|
||||||
|
splitHook: value => {
|
||||||
|
const v = value.trim();
|
||||||
|
if (v && inputDelimiters.includes(v.substr(-1))) {
|
||||||
|
return EmailModel.splitEmailLine(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
parseHook: input =>
|
||||||
|
input.map(inputValue => {
|
||||||
|
const values = EmailModel.parseEmailLine(inputValue);
|
||||||
|
return values.length ? values : inputValue;
|
||||||
|
}).flat(Infinity).map(
|
||||||
|
item => (item.toLine ? [item.toLine(false), item] : [item, null])
|
||||||
|
),
|
||||||
|
change: event => {
|
||||||
|
element.EmailsTagsValue = event.target.value;
|
||||||
|
fValue(event.target.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fValue && fValue.focused && fValue.focused.subscribe) {
|
||||||
|
fValue.focused.subscribe((value) => {
|
||||||
|
$el.inputosaurus(value ? 'focus' : 'blur');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: (element, fValueAccessor) => {
|
||||||
|
const value = ko.unwrap(fValueAccessor());
|
||||||
|
|
||||||
|
if (element.EmailsTagsValue !== value) {
|
||||||
|
element.value = value;
|
||||||
|
element.EmailsTagsValue = value;
|
||||||
|
$(element).inputosaurus('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.draggable = {
|
||||||
|
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
||||||
|
const Utils = require('Common/Utils');
|
||||||
|
|
||||||
|
if (!bMobileDevice) {
|
||||||
|
const triggerZone = 50,
|
||||||
|
scrollSpeed = 3,
|
||||||
|
fAllValueFunc = fAllBindingsAccessor(),
|
||||||
|
selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '',
|
||||||
|
droppable = selector ? document.querySelector(selector) : null,
|
||||||
|
conf = {
|
||||||
|
distance: 20,
|
||||||
|
handle: '.dragHandle',
|
||||||
|
cursorAt: { top: 22, left: 3 },
|
||||||
|
refreshPositions: true,
|
||||||
|
scroll: true,
|
||||||
|
drag: null,
|
||||||
|
stop: null,
|
||||||
|
helper: null
|
||||||
|
};
|
||||||
|
let bcr;
|
||||||
|
|
||||||
|
if (droppable) {
|
||||||
|
conf.drag = event => {
|
||||||
|
if (droppable.scrollTopMax) {
|
||||||
|
clearInterval(droppable.timerScroll);
|
||||||
|
if (droppable.scrollTop
|
||||||
|
&& bcr.top < event.clientY
|
||||||
|
&& bcr.top + triggerZone > event.clientY)
|
||||||
|
{
|
||||||
|
droppable.timerScroll = setInterval(() => droppable.scrollTop -= scrollSpeed, 10);
|
||||||
|
}
|
||||||
|
else if (droppable.scrollTop < droppable.scrollTopMax
|
||||||
|
&& bcr.bottom > event.clientY
|
||||||
|
&& bcr.bottom - triggerZone < event.clientY)
|
||||||
|
{
|
||||||
|
droppable.timerScroll = setInterval(() => droppable.scrollTop += scrollSpeed, 10);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clearInterval(droppable.timerScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
conf.stop = () => clearInterval(droppable.timerScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.helper = event => fValueAccessor()(event && event.target ? ko.dataFor(event.target) : null);
|
||||||
|
|
||||||
|
$(element)
|
||||||
|
.draggable(conf)
|
||||||
|
.on('mousedown.koDraggable', () => {
|
||||||
|
Utils.removeInFocus();
|
||||||
|
bcr = droppable ? droppable.getBoundingClientRect() : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
ko.utils.domNodeDisposal.addDisposeCallback(element, () =>
|
||||||
|
$(element)
|
||||||
|
.off('mousedown.koDraggable')
|
||||||
|
.draggable('destroy')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.droppable = {
|
||||||
|
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
||||||
|
if (!bMobileDevice) {
|
||||||
|
const fValueFunc = fValueAccessor(),
|
||||||
|
fAllValueFunc = fAllBindingsAccessor(),
|
||||||
|
fOverCallback = fAllValueFunc && fAllValueFunc.droppableOver ? fAllValueFunc.droppableOver : null,
|
||||||
|
fOutCallback = fAllValueFunc && fAllValueFunc.droppableOut ? fAllValueFunc.droppableOut : null,
|
||||||
|
conf = {
|
||||||
|
tolerance: 'pointer',
|
||||||
|
hoverClass: 'droppableHover',
|
||||||
|
drop: null,
|
||||||
|
over: null,
|
||||||
|
out: null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fValueFunc) {
|
||||||
|
conf.drop = (event, ui) => {
|
||||||
|
fValueFunc(event, ui);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fOverCallback) {
|
||||||
|
conf.over = (event, ui) => {
|
||||||
|
fOverCallback(event, ui);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fOutCallback) {
|
||||||
|
conf.out = (event, ui) => {
|
||||||
|
fOutCallback(event, ui);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$(element).droppable(conf);
|
||||||
|
|
||||||
|
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
||||||
|
$(element).droppable('destroy');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.link = {
|
||||||
|
update: (element, fValueAccessor) => element.href = ko.unwrap(fValueAccessor())
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.initDom = {
|
||||||
|
init: (element, fValueAccessor) => fValueAccessor()(element)
|
||||||
|
};
|
||||||
|
|
||||||
|
ko.bindingHandlers.onEsc = {
|
||||||
|
init: (element, fValueAccessor, fAllBindingsAccessor, viewModel) => {
|
||||||
|
$(element).on('keyup.koOnEsc', (event) => {
|
||||||
|
if (event && 27 === parseInt(event.keyCode, 10)) {
|
||||||
|
$(element).trigger('change');
|
||||||
|
fValueAccessor().call(viewModel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
||||||
|
$(element).off('keyup.koOnEsc');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// extenders
|
||||||
|
|
||||||
|
ko.extenders.specialThrottle = (target, option) => {
|
||||||
|
target.iSpecialThrottleTimeoutValue = require('Common/Utils').pInt(option);
|
||||||
|
if (0 < target.iSpecialThrottleTimeoutValue) {
|
||||||
|
target.iSpecialThrottleTimeout = 0;
|
||||||
|
target.valueForRead = ko.observable(!!target()).extend({ throttle: 10 });
|
||||||
|
|
||||||
|
return ko.computed({
|
||||||
|
read: target.valueForRead,
|
||||||
|
write: (bValue) => {
|
||||||
|
if (bValue) {
|
||||||
|
target.valueForRead(bValue);
|
||||||
|
} else if (target.valueForRead()) {
|
||||||
|
clearTimeout(target.iSpecialThrottleTimeout);
|
||||||
|
target.iSpecialThrottleTimeout = setTimeout(() => {
|
||||||
|
target.valueForRead(false);
|
||||||
|
target.iSpecialThrottleTimeout = 0;
|
||||||
|
}, target.iSpecialThrottleTimeoutValue);
|
||||||
|
} else {
|
||||||
|
target.valueForRead(bValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
// functions
|
||||||
|
|
||||||
|
ko.observable.fn.validateEmail = function() {
|
||||||
|
this.hasError = ko.observable(false);
|
||||||
|
|
||||||
|
this.subscribe(value => this.hasError(value && !/^[^@\s]+@[^@\s]+$/.test(value)));
|
||||||
|
|
||||||
|
this.valueHasMutated();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ko;
|
||||||
340
dev/External/ko.js
vendored
340
dev/External/ko.js
vendored
|
|
@ -1,44 +1,17 @@
|
||||||
import { SaveSettingsStep } from 'Common/Enums';
|
|
||||||
|
|
||||||
const
|
const
|
||||||
$ = jQuery,
|
$ = jQuery,
|
||||||
doc = document,
|
doc = document,
|
||||||
ko = window.ko,
|
ko = window.ko,
|
||||||
Translator = () => require('Common/Translator'),
|
Translator = () => require('Common/Translator'),
|
||||||
|
Globals = () => require('Common/Globals'),
|
||||||
isFunction = v => typeof v === 'function';
|
isFunction = v => typeof v === 'function';
|
||||||
|
|
||||||
ko.bindingHandlers.editor = {
|
|
||||||
init: (element, fValueAccessor) => {
|
|
||||||
let editor = null;
|
|
||||||
|
|
||||||
const fValue = fValueAccessor(),
|
|
||||||
HtmlEditor = require('Common/HtmlEditor').default,
|
|
||||||
fUpdateEditorValue = () => fValue && fValue.__editor && fValue.__editor.setHtmlOrPlain(fValue()),
|
|
||||||
fUpdateKoValue = () => fValue && fValue.__editor && fValue(fValue.__editor.getDataWithHtmlMark()),
|
|
||||||
fOnReady = () => {
|
|
||||||
fValue.__editor = editor;
|
|
||||||
fUpdateEditorValue();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ko.isObservable(fValue) && HtmlEditor) {
|
|
||||||
editor = new HtmlEditor(element, fUpdateKoValue, fOnReady, fUpdateKoValue);
|
|
||||||
|
|
||||||
fValue.__fetchEditorValue = fUpdateKoValue;
|
|
||||||
|
|
||||||
fValue.subscribe(fUpdateEditorValue);
|
|
||||||
|
|
||||||
// ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.tooltip = {
|
ko.bindingHandlers.tooltip = {
|
||||||
init: (element, fValueAccessor) => {
|
init: (element, fValueAccessor) => {
|
||||||
const fValue = fValueAccessor(),
|
const fValue = fValueAccessor(),
|
||||||
Globals = require('Common/Globals');
|
Global = Globals();
|
||||||
|
|
||||||
if (!Globals.bMobileDevice || 'on' === element.dataset.tooltipMobile) {
|
if (!Global.bMobileDevice || 'on' === element.dataset.tooltipMobile) {
|
||||||
const sValue = !ko.isObservable(fValue) && isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
const sValue = !ko.isObservable(fValue) && isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
||||||
|
|
||||||
if ('off' === element.dataset.tooltipI18n) {
|
if ('off' === element.dataset.tooltipI18n) {
|
||||||
|
|
@ -48,17 +21,16 @@ ko.bindingHandlers.tooltip = {
|
||||||
Translator().trigger.subscribe(() =>
|
Translator().trigger.subscribe(() =>
|
||||||
element.title = Translator().i18n(sValue)
|
element.title = Translator().i18n(sValue)
|
||||||
);
|
);
|
||||||
Globals.dropdownVisibility.subscribe(() =>
|
Global.dropdownVisibility.subscribe(() =>
|
||||||
element.title = Translator().i18n(sValue)
|
element.title = Translator().i18n(sValue)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: (element, fValueAccessor) => {
|
update: (element, fValueAccessor) => {
|
||||||
const fValue = fValueAccessor(),
|
const fValue = fValueAccessor();
|
||||||
Globals = require('Common/Globals');
|
|
||||||
|
|
||||||
if (!Globals.bMobileDevice || 'on' === element.dataset.tooltipMobile) {
|
if (!Globals().bMobileDevice || 'on' === element.dataset.tooltipMobile) {
|
||||||
const sValue = !ko.isObservable(fValue) && isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
const sValue = !ko.isObservable(fValue) && isFunction(fValue) ? fValue() : ko.unwrap(fValue);
|
||||||
if (sValue) {
|
if (sValue) {
|
||||||
element.title = 'off' === element.dataset.tooltipI18n ? sValue : Translator().i18n(sValue);
|
element.title = 'off' === element.dataset.tooltipI18n ? sValue : Translator().i18n(sValue);
|
||||||
|
|
@ -148,26 +120,9 @@ ko.bindingHandlers.onSpace = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.bindingHandlers.onEsc = {
|
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor, viewModel) => {
|
|
||||||
$(element).on('keyup.koOnEsc', (event) => {
|
|
||||||
if (event && 27 === parseInt(event.keyCode, 10)) {
|
|
||||||
$(element).trigger('change');
|
|
||||||
fValueAccessor().call(viewModel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
|
||||||
$(element).off('keyup.koOnEsc');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.modal = {
|
ko.bindingHandlers.modal = {
|
||||||
init: (element, fValueAccessor) => {
|
init: (element, fValueAccessor) => {
|
||||||
const Globals = require('Common/Globals');
|
element.classList.toggle('fade', !Globals().bMobileDevice);
|
||||||
|
|
||||||
element.classList.toggle('fade', !Globals.bMobileDevice);
|
|
||||||
|
|
||||||
const close = element.querySelector('.close'), click = () => fValueAccessor()(false);
|
const close = element.querySelector('.close'), click = () => fValueAccessor()(false);
|
||||||
close && close.addEventListener('click.koModal', click);
|
close && close.addEventListener('click.koModal', click);
|
||||||
|
|
@ -178,12 +133,6 @@ ko.bindingHandlers.modal = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ttn = (element, fValueAccessor) => require('Common/Momentor').timeToNode(element, ko.unwrap(fValueAccessor()));
|
|
||||||
ko.bindingHandlers.moment = {
|
|
||||||
init: ttn,
|
|
||||||
update: ttn
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.i18nInit = {
|
ko.bindingHandlers.i18nInit = {
|
||||||
init: element => Translator().i18nToNodes(element)
|
init: element => Translator().i18nToNodes(element)
|
||||||
};
|
};
|
||||||
|
|
@ -195,212 +144,10 @@ ko.bindingHandlers.i18nUpdate = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.bindingHandlers.link = {
|
|
||||||
update: (element, fValueAccessor) => element.href = ko.unwrap(fValueAccessor())
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.title = {
|
ko.bindingHandlers.title = {
|
||||||
update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor())
|
update: (element, fValueAccessor) => element.title = ko.unwrap(fValueAccessor())
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.bindingHandlers.initDom = {
|
|
||||||
init: (element, fValueAccessor) => fValueAccessor()(element)
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.draggable = {
|
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
|
||||||
const Globals = require('Common/Globals'),
|
|
||||||
Utils = require('Common/Utils');
|
|
||||||
|
|
||||||
if (!Globals.bMobileDevice) {
|
|
||||||
const triggerZone = 50,
|
|
||||||
scrollSpeed = 3,
|
|
||||||
fAllValueFunc = fAllBindingsAccessor(),
|
|
||||||
selector = fAllValueFunc ? fAllValueFunc.droppableSelector : '',
|
|
||||||
droppable = selector ? doc.querySelector(selector) : null,
|
|
||||||
conf = {
|
|
||||||
distance: 20,
|
|
||||||
handle: '.dragHandle',
|
|
||||||
cursorAt: { top: 22, left: 3 },
|
|
||||||
refreshPositions: true,
|
|
||||||
scroll: true,
|
|
||||||
drag: null,
|
|
||||||
stop: null,
|
|
||||||
helper: null
|
|
||||||
};
|
|
||||||
let bcr;
|
|
||||||
|
|
||||||
if (droppable) {
|
|
||||||
conf.drag = event => {
|
|
||||||
if (droppable.scrollTopMax) {
|
|
||||||
clearInterval(droppable.timerScroll);
|
|
||||||
if (droppable.scrollTop
|
|
||||||
&& bcr.top < event.clientY
|
|
||||||
&& bcr.top + triggerZone > event.clientY)
|
|
||||||
{
|
|
||||||
droppable.timerScroll = setInterval(() => droppable.scrollTop -= scrollSpeed, 10);
|
|
||||||
}
|
|
||||||
else if (droppable.scrollTop < droppable.scrollTopMax
|
|
||||||
&& bcr.bottom > event.clientY
|
|
||||||
&& bcr.bottom - triggerZone < event.clientY)
|
|
||||||
{
|
|
||||||
droppable.timerScroll = setInterval(() => droppable.scrollTop += scrollSpeed, 10);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInterval(droppable.timerScroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
conf.stop = () => clearInterval(droppable.timerScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.helper = event => fValueAccessor()(event && event.target ? ko.dataFor(event.target) : null);
|
|
||||||
|
|
||||||
$(element)
|
|
||||||
.draggable(conf)
|
|
||||||
.on('mousedown.koDraggable', () => {
|
|
||||||
Utils.removeInFocus();
|
|
||||||
bcr = droppable ? droppable.getBoundingClientRect() : null;
|
|
||||||
});
|
|
||||||
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () =>
|
|
||||||
$(element)
|
|
||||||
.off('mousedown.koDraggable')
|
|
||||||
.draggable('destroy')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.droppable = {
|
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
|
||||||
const Globals = require('Common/Globals');
|
|
||||||
if (!Globals.bMobileDevice) {
|
|
||||||
const fValueFunc = fValueAccessor(),
|
|
||||||
fAllValueFunc = fAllBindingsAccessor(),
|
|
||||||
fOverCallback = fAllValueFunc && fAllValueFunc.droppableOver ? fAllValueFunc.droppableOver : null,
|
|
||||||
fOutCallback = fAllValueFunc && fAllValueFunc.droppableOut ? fAllValueFunc.droppableOut : null,
|
|
||||||
conf = {
|
|
||||||
tolerance: 'pointer',
|
|
||||||
hoverClass: 'droppableHover',
|
|
||||||
drop: null,
|
|
||||||
over: null,
|
|
||||||
out: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fValueFunc) {
|
|
||||||
conf.drop = (event, ui) => {
|
|
||||||
fValueFunc(event, ui);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fOverCallback) {
|
|
||||||
conf.over = (event, ui) => {
|
|
||||||
fOverCallback(event, ui);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fOutCallback) {
|
|
||||||
conf.out = (event, ui) => {
|
|
||||||
fOutCallback(event, ui);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
$(element).droppable(conf);
|
|
||||||
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
|
|
||||||
$(element).droppable('destroy');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.saveTrigger = {
|
|
||||||
init: (element) => {
|
|
||||||
element.saveTriggerType = element.matches('input[type=text],input[type=email],input[type=password],select,textarea')
|
|
||||||
? 'input' : 'custom';
|
|
||||||
|
|
||||||
if ('custom' === element.saveTriggerType) {
|
|
||||||
element.append(
|
|
||||||
' ',
|
|
||||||
Element.fromHTML('<i class="icon-spinner animated"></i>'),
|
|
||||||
Element.fromHTML('<i class="icon-remove error"></i>'),
|
|
||||||
Element.fromHTML('<i class="icon-ok success"></i>')
|
|
||||||
);
|
|
||||||
element.classList.add('settings-saved-trigger');
|
|
||||||
} else {
|
|
||||||
element.classList.add('settings-saved-trigger-input');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: (element, fValueAccessor) => {
|
|
||||||
const value = parseInt(ko.unwrap(fValueAccessor()),10);
|
|
||||||
if ('custom' === element.saveTriggerType) {
|
|
||||||
element.querySelector('.animated').hidden = value !== SaveSettingsStep.Animate;
|
|
||||||
element.querySelector('.success').hidden = value !== SaveSettingsStep.TrueResult;
|
|
||||||
element.querySelector('.error').hidden = value !== SaveSettingsStep.FalseResult;
|
|
||||||
} else if (value !== SaveSettingsStep.Animate) {
|
|
||||||
element.classList.toggle('success', value === SaveSettingsStep.TrueResult);
|
|
||||||
element.classList.toggle('error', value === SaveSettingsStep.FalseResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.emailsTags = {
|
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor) => {
|
|
||||||
const EmailModel = require('Model/Email').default,
|
|
||||||
$el = $(element),
|
|
||||||
fValue = fValueAccessor(),
|
|
||||||
fAllBindings = fAllBindingsAccessor(),
|
|
||||||
inputDelimiters = [',', ';', '\n'];
|
|
||||||
|
|
||||||
$el.inputosaurus({
|
|
||||||
parseOnBlur: true,
|
|
||||||
allowDragAndDrop: true,
|
|
||||||
focusCallback: value => {
|
|
||||||
if (fValue && fValue.focused) {
|
|
||||||
fValue.focused(!!value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inputDelimiters: inputDelimiters,
|
|
||||||
autoCompleteSource: fAllBindings.autoCompleteSource || null,
|
|
||||||
splitHook: value => {
|
|
||||||
const v = value.trim();
|
|
||||||
if (v && inputDelimiters.includes(v.substr(-1))) {
|
|
||||||
return EmailModel.splitEmailLine(value);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
parseHook: input =>
|
|
||||||
input.map(inputValue => {
|
|
||||||
const values = EmailModel.parseEmailLine(inputValue);
|
|
||||||
return values.length ? values : inputValue;
|
|
||||||
}).flat(Infinity).map(
|
|
||||||
item => (item.toLine ? [item.toLine(false), item] : [item, null])
|
|
||||||
),
|
|
||||||
change: event => {
|
|
||||||
element.EmailsTagsValue = event.target.value;
|
|
||||||
fValue(event.target.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fValue && fValue.focused && fValue.focused.subscribe) {
|
|
||||||
fValue.focused.subscribe((value) => {
|
|
||||||
$el.inputosaurus(value ? 'focus' : 'blur');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: (element, fValueAccessor) => {
|
|
||||||
const value = ko.unwrap(fValueAccessor());
|
|
||||||
|
|
||||||
if (element.EmailsTagsValue !== value) {
|
|
||||||
element.value = value;
|
|
||||||
element.EmailsTagsValue = value;
|
|
||||||
$(element).inputosaurus('refresh');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.bindingHandlers.command = {
|
ko.bindingHandlers.command = {
|
||||||
init: (element, fValueAccessor, fAllBindingsAccessor, viewModel, bindingContext) => {
|
init: (element, fValueAccessor, fAllBindingsAccessor, viewModel, bindingContext) => {
|
||||||
const command = fValueAccessor();
|
const command = fValueAccessor();
|
||||||
|
|
@ -455,28 +202,6 @@ ko.bindingHandlers.command = {
|
||||||
|
|
||||||
// extenders
|
// extenders
|
||||||
|
|
||||||
ko.extenders.posInterer = (target, defaultVal) => {
|
|
||||||
const Utils = require('Common/Utils'),
|
|
||||||
result = ko.computed({
|
|
||||||
read: target,
|
|
||||||
write: (newValue) => {
|
|
||||||
let val = Utils.pInt(newValue.toString(), defaultVal);
|
|
||||||
if (0 >= val) {
|
|
||||||
val = defaultVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val === target() && '' + val !== '' + newValue) {
|
|
||||||
target(val + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
target(val);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
result(target());
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.extenders.limitedList = (target, limitedList) => {
|
ko.extenders.limitedList = (target, limitedList) => {
|
||||||
const result = ko
|
const result = ko
|
||||||
.computed({
|
.computed({
|
||||||
|
|
@ -522,12 +247,6 @@ ko.extenders.reversible = (target) => {
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.extenders.toggleSubscribe = (target, options) => {
|
|
||||||
target.subscribe(options[1], options[0], 'beforeChange');
|
|
||||||
target.subscribe(options[2], options[0]);
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.extenders.toggleSubscribeProperty = (target, options) => {
|
ko.extenders.toggleSubscribeProperty = (target, options) => {
|
||||||
const prop = options[1];
|
const prop = options[1];
|
||||||
if (prop) {
|
if (prop) {
|
||||||
|
|
@ -558,53 +277,8 @@ ko.extenders.falseTimeout = (target, option) => {
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
ko.extenders.specialThrottle = (target, option) => {
|
|
||||||
target.iSpecialThrottleTimeoutValue = require('Common/Utils').pInt(option);
|
|
||||||
if (0 < target.iSpecialThrottleTimeoutValue) {
|
|
||||||
target.iSpecialThrottleTimeout = 0;
|
|
||||||
target.valueForRead = ko.observable(!!target()).extend({ throttle: 10 });
|
|
||||||
|
|
||||||
return ko.computed({
|
|
||||||
read: target.valueForRead,
|
|
||||||
write: (bValue) => {
|
|
||||||
if (bValue) {
|
|
||||||
target.valueForRead(bValue);
|
|
||||||
} else if (target.valueForRead()) {
|
|
||||||
clearTimeout(target.iSpecialThrottleTimeout);
|
|
||||||
target.iSpecialThrottleTimeout = setTimeout(() => {
|
|
||||||
target.valueForRead(false);
|
|
||||||
target.iSpecialThrottleTimeout = 0;
|
|
||||||
}, target.iSpecialThrottleTimeoutValue);
|
|
||||||
} else {
|
|
||||||
target.valueForRead(bValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.extenders.idleTrigger = (target) => {
|
|
||||||
target.trigger = ko.observable(SaveSettingsStep.Idle);
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
ko.observable.fn.idleTrigger = function() {
|
|
||||||
return this.extend({ 'idleTrigger': true });
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.observable.fn.validateEmail = function() {
|
|
||||||
this.hasError = ko.observable(false);
|
|
||||||
|
|
||||||
this.subscribe(value => this.hasError(value && !/^[^@\s]+@[^@\s]+$/.test(value)));
|
|
||||||
|
|
||||||
this.valueHasMutated();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
ko.observable.fn.deleteAccessHelper = function() {
|
ko.observable.fn.deleteAccessHelper = function() {
|
||||||
this.extend({ falseTimeout: 3000 }).extend({ toggleSubscribeProperty: [this, 'deleteAccess'] });
|
this.extend({ falseTimeout: 3000 }).extend({ toggleSubscribeProperty: [this, 'deleteAccess'] });
|
||||||
return this;
|
return this;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@ import { AbstractViewNext } from 'Knoin/AbstractViewNext';
|
||||||
|
|
||||||
const Settings = rl.settings;
|
const Settings = rl.settings;
|
||||||
|
|
||||||
|
ko.extenders.toggleSubscribe = (target, options) => {
|
||||||
|
target.subscribe(options[1], options[0], 'beforeChange');
|
||||||
|
target.subscribe(options[2], options[0]);
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
@popup({
|
@popup({
|
||||||
name: 'View/Popup/Compose',
|
name: 'View/Popup/Compose',
|
||||||
templateID: 'PopupsCompose'
|
templateID: 'PopupsCompose'
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,15 @@ import Remote from 'Remote/User/Fetch';
|
||||||
import { popup, command } from 'Knoin/Knoin';
|
import { popup, command } from 'Knoin/Knoin';
|
||||||
import { AbstractViewNext } from 'Knoin/AbstractViewNext';
|
import { AbstractViewNext } from 'Knoin/AbstractViewNext';
|
||||||
|
|
||||||
|
ko.observable.fn.validateEmail = function() {
|
||||||
|
this.hasError = ko.observable(false);
|
||||||
|
|
||||||
|
this.subscribe(value => this.hasError(value && !/^[^@\s]+@[^@\s]+$/.test(value)));
|
||||||
|
|
||||||
|
this.valueHasMutated();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
@popup({
|
@popup({
|
||||||
name: 'View/Popup/Identity',
|
name: 'View/Popup/Identity',
|
||||||
templateID: 'PopupsIdentity'
|
templateID: 'PopupsIdentity'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue