snappymail/dev/External/ko.js
the-djmaze f50f2c5ea0 decorateKoCommands() each command must have function
And due to that, a bug is found and solved in MessageView
2022-03-03 17:34:45 +01:00

204 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ko from 'ko';
import { i18nToNodes } from 'Common/Translator';
import { doc, createElement } from 'Common/Globals';
import { SaveSettingsStep } from 'Common/Enums';
import { arrayLength, isFunction, forEachObjectEntry } from 'Common/Utils';
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
* number of evaluations or other “hidden” information. Its value should be
* based solely on the values of other observables in the application
*/
koComputable = fn => ko.computed(fn, {'pure':true}),
addObservablesTo = (target, observables) =>
forEachObjectEntry(observables, (key, value) =>
target[key] || (target[key] = /*isArray(value) ? ko.observableArray(value) :*/ ko.observable(value)) ),
addComputablesTo = (target, computables) =>
forEachObjectEntry(computables, (key, fn) => target[key] = koComputable(fn)),
addSubscribablesTo = (target, subscribables) =>
forEachObjectEntry(subscribables, (key, fn) => target[key].subscribe(fn)),
dispose = disposable => disposable && isFunction(disposable.dispose) && disposable.dispose(),
// With this we don't need delegateRunOnDestroy
koArrayWithDestroy = data => {
data = ko.observableArray(data);
data.subscribe(changes =>
changes.forEach(item =>
'deleted' === item.status && null == item.moved && item.value.onDestroy && item.value.onDestroy()
)
, data, 'arrayChange');
return data;
};
Object.assign(ko.bindingHandlers, {
tooltipErrorTip: {
init: (element, fValueAccessor) => {
doc.addEventListener('click', () => {
let value = fValueAccessor();
ko.isObservable(value) && !ko.isComputed(value) && value('');
errorTip(element);
});
},
update: (element, fValueAccessor) => {
let value = ko.unwrap(fValueAccessor());
value = isFunction(value) ? value() : value;
errorTip(element, value);
}
},
onEnter: {
init: (element, fValueAccessor, fAllBindings, viewModel) => {
let fn = event => {
if ('Enter' == event.key) {
element.dispatchEvent(new Event('change'));
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.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.canExecute();
cl.toggle('disabled', disabled);
if (element.matches('INPUT,TEXTAREA,BUTTON')) {
element.disabled = disabled;
}
}
},
saveTrigger: {
init: (element) => {
let icon = element;
if (element.matches('input,select,textarea')) {
element.classList.add('settings-saved-trigger-input');
element.after(element.saveTriggerIcon = icon = createElement('span'));
}
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('error', value === SaveSettingsStep.FalseResult);
}
cl = element.classList;
cl.toggle('success', value === SaveSettingsStep.TrueResult);
cl.toggle('error', value === SaveSettingsStep.FalseResult);
}
}
});
// extenders
ko.extenders.limitedList = (target, limitedList) => {
const result = ko
.computed({
read: target,
write: newValue => {
let currentValue = target(),
list = ko.unwrap(limitedList);
list = arrayLength(list) ? list : [''];
if (!list.includes(newValue)) {
newValue = list.includes(currentValue, list) ? currentValue : list[0];
target(newValue + ' ');
}
target(newValue);
}
})
.extend({ notify: 'always' });
result(target());
if (!result.valueHasMutated) {
result.valueHasMutated = () => target.valueHasMutated();
}
return result;
};
ko.extenders.toggleSubscribeProperty = (target, options) => {
const prop = options[1];
if (prop) {
target.subscribe(
prev => prev && prev[prop] && prev[prop](false),
options[0],
'beforeChange'
);
target.subscribe(next => next && next[prop] && next[prop](true), options[0]);
}
return target;
};
ko.extenders.falseTimeout = (target, option) => {
target.subscribe((() => target(false)).debounce(parseInt(option, 10) || 0));
return target;
};
// functions
ko.observable.fn.askDeleteHelper = function() {
return this.extend({ falseTimeout: 3000, toggleSubscribeProperty: [this, 'askDelete'] });
};