mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-11-03 19:08:50 +03:00
Update Fork (#2)
* Cleanup OpenPgpImportPopupView code * update polish translation * small fix * Added Import S/MIME certificate popup And much better handling of the sign and encrypt options * bugfix: store in Passphrases * Resolve #1448 * pre-verify S/MIME opaque signed messages so we have a body to view * Fix timestampToString() for future dates * Move php8.php to /app/libraries/polyfill/ * Improved Settings handling to prevent bugs in outer code * Changed AbstractProvider::IsActive() to be abstract * Example for #1449 * bugfix: previous IsActive() commit * OpenSSL required due to S/MIME * Use get_debug_type() instead of gettype() * update polish translation * Make all Enumerations classes abstract * Added search functionality in Admin -> Config And removed the unused ['capa']['quota'] * Cleanup Quota handling * OPEN_PGP should be OPENPGP as it is one word * Improve Capa handling * Resolve #1451 * Bugfix TypeError: b64Encode(...).match(...) is null * Small StorageType change * Bugfix: mailvelope editor failed * Bugfix: undefined getMailvelopePrivateKeyFor() * Bugfix: MIME parser RegExp didn't escape `boundary` which caused issues * Return detailed info on PgpImportKey * Show GnuPG verify error * Sort PGP keys by email and id * Sort S/MIME certificates on emailAddress else validTo * S/MIME import from signature use `BEGIN PKCS7` * Optionally use existing private key to generate S/MIME certificate * Chaned some error_log() to MailSo Logger() * Force reload of S/MIME certificates list on import * Make better use of SnappyMail\SensitiveString * Fix view PGP key button * Mask all POST data that has a key which contains `pass` * v2.35.1 * Resolve #1455 * Improved GnuPG error handling * Update pt/pt-PT translation * update Polish translation * Drop support for gnupg pecl extension as it fails with "no passphrase" issues * Resolve #1456 * Resolve #1458 * v2.35.2 * fix changelog * Resolve #1461 * Update pt/pt-PT translation * compact-composer plugin v1.0.0 * Resolve #1462 * Fix decrypt error message * `new Error()` to `Error()` * Resolve #1463 * Show url for #1466 * Simplify SignMe/Remember me code * Simplify language Notifications * Bugfix: SetPassword expects \SnappyMail\SensitiveString * https://github.com/the-djmaze/snappymail/issues/1450#issuecomment-1972147950 * improve: fire the 'squire2-toolbar' event after more props are added * improve: add dark theme support and use 'button' element as menu trigger for consistent styling * fix: use compact template in non-destructive way (do not replace the PopupsCompose template if a different wysiwyg is used) * Update admin.json * Update user.json * CSS rainloopErrorTip location * Improved error handling on PGP and S/MIME decrypt * KnockoutJS remove unused `beforeRemove` * KnockoutJS drop unused `as` * KnockoutJS simplify renderMode because only 1 option is used * KnoutJS cleanup templating.js a bit * KnockoutJS drop unused `bindingRewriteValidators` * KnockoutJS drop the twoWayBindings code * KnockoutJS simplify virtualElements binding check * KnockoutJS simplify applyBindingsToNodeInternal * KnockoutJS use Array.isArray * KnockoutJS drop alias `textinput` for `textInput` * KnockoutJS scramble `createChildContext` * KnockoutJS scramble `controlsDescendantBindings` * KnockoutJS scramble `exportDependencies` * KnockoutJS drop unused `throttleEvaluation` * KnockoutJS drop unused `valueAllowUnset` * KnockoutJS drop unused `templateNodes` * KnockoutJS drop unused `optionsCaption` * KnockoutJS drop unused `dontLimitMoves` * KnockoutJS drop unused `uniqueName` * KnockoutJS drop IE leftovers * KnockoutJS drop unused `preprocess` * KnockoutJS drop unused "disposeWhenNodeIsRemoved" and "disposeWhen" * KnockoutJS don't scramble exportDependencies. controlsDescendantBindings, createChildContext * KnockoutJS drop unused `$parentContext` and `$parents` * KnockoutJS drop unused `$rawData` * Knockoutjs built latest * KnockoutJS drop unused template options `nodes`, `if`, `ifnot` * KnockoutJS use more Array.isArray * KnockoutJS cleanup code a bit * KnockoutJS primitiveTypes can just be checked with Object() * KnockoutJS rebuilt * Verify S/MIME signed automatically and log Exception * Automatically verify PGP and S/MIME signed messages * `new Error` to `Error` * By default throw AccountNotAllowed as confused in #1478 * GPG use pinentries for decrypt, sign and export * Better GPG error handling * GPG show error on view/export * OpenPGP fix handling of importing keys * Make "verify signatures automatically" optional, as it requires more IMAP fetching * S/MIME don't post identity key and certificate, just fetch from server * Show error to old browsers, instead of crashing * Automatically verify S/MIME decrypted signed message --------- Co-authored-by: the-djmaze <> Co-authored-by: tinola <tinola@poczta.onet.pl> Co-authored-by: Maarten <3752035+the-djmaze@users.noreply.github.com> Co-authored-by: lmperfis <joint.striker@gmail.com> Co-authored-by: Sergey Mosin <sergey@srgdev.com> Co-authored-by: hguilbert <51283484+hguilbert@users.noreply.github.com>
This commit is contained in:
parent
3bd073a38b
commit
876ed221c3
218 changed files with 10266 additions and 4267 deletions
|
|
@ -13,7 +13,7 @@ ko.bindingHandlers = {};
|
|||
// The ko.bindingContext constructor is only called directly to create the root context. For child
|
||||
// contexts, use bindingContext.createChildContext or bindingContext.extend.
|
||||
ko.bindingContext = class {
|
||||
constructor(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options)
|
||||
constructor(dataItemOrAccessor, parentContext, extendCallback, options)
|
||||
{
|
||||
var self = this,
|
||||
shouldInheritData = dataItemOrAccessor === inheritParentVm,
|
||||
|
|
@ -42,7 +42,6 @@ ko.bindingContext = class {
|
|||
self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
|
||||
}
|
||||
} else {
|
||||
self['$parents'] = [];
|
||||
self['$root'] = dataItem;
|
||||
|
||||
// Export 'ko' in the binding context so it will be available in bindings and templates
|
||||
|
|
@ -56,13 +55,9 @@ ko.bindingContext = class {
|
|||
if (shouldInheritData) {
|
||||
dataItem = self['$data'];
|
||||
} else {
|
||||
self['$rawData'] = dataItemOrObservable;
|
||||
self['$data'] = dataItem;
|
||||
}
|
||||
|
||||
if (dataItemAlias)
|
||||
self[dataItemAlias] = dataItem;
|
||||
|
||||
// The extendCallback function is provided when creating a child context or extending a context.
|
||||
// It handles the specific actions needed to finish setting up the binding context. Actions in this
|
||||
// function could also add dependencies to this binding context.
|
||||
|
|
@ -107,21 +102,11 @@ ko.bindingContext = class {
|
|||
// But this does not mean that the $data value of the child context will also get updated. If the child
|
||||
// view model also depends on the parent view model, you must provide a function that returns the correct
|
||||
// view model on each update.
|
||||
'createChildContext'(dataItemOrAccessor, dataItemAlias, extendCallback, options) {
|
||||
if (!options && dataItemAlias && typeof dataItemAlias == "object") {
|
||||
options = dataItemAlias;
|
||||
dataItemAlias = options['as'];
|
||||
extendCallback = options['extend'];
|
||||
}
|
||||
|
||||
return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, (self, parentContext) => {
|
||||
'createChildContext'(dataItemOrAccessor, options) {
|
||||
return new ko.bindingContext(dataItemOrAccessor, this, (self, parentContext) => {
|
||||
// Extend the context hierarchy by setting the appropriate pointers
|
||||
self['$parentContext'] = parentContext;
|
||||
self['$parent'] = parentContext['$data'];
|
||||
self['$parents'] = (parentContext['$parents'] || []).slice(0);
|
||||
self['$parents'].unshift(self['$parent']);
|
||||
if (extendCallback)
|
||||
extendCallback(self);
|
||||
options['extend']?.(self);
|
||||
}, options);
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +114,7 @@ ko.bindingContext = class {
|
|||
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
|
||||
// when an observable view model is updated.
|
||||
'extend'(properties, options) {
|
||||
return new ko.bindingContext(inheritParentVm, this, null, self =>
|
||||
return new ko.bindingContext(inheritParentVm, this, self =>
|
||||
ko.utils.extend(self, typeof(properties) == "function" ? properties(self) : properties)
|
||||
, options);
|
||||
}
|
||||
|
|
@ -202,7 +187,7 @@ ko.bindingEvent = {
|
|||
} else if (bindingInfo.asyncContext === undefined && bindingInfo.eventSubscribable?.hasSubscriptionsForEvent(ko.bindingEvent.descendantsComplete)) {
|
||||
// It's currently an error to register a descendantsComplete handler for a node that was never registered as completing asynchronously.
|
||||
// That's because without the asyncContext, we don't have a way to know that all descendants have completed.
|
||||
throw new Error("descendantsComplete event not supported for bindings on this node");
|
||||
throw Error("descendantsComplete event not supported for bindings on this node");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,12 +211,6 @@ ko.bindingEvent = {
|
|||
}
|
||||
};
|
||||
|
||||
function validateThatBindingIsAllowedForVirtualElements(bindingName) {
|
||||
var validator = ko.virtualElements.allowedBindings[bindingName];
|
||||
if (!validator)
|
||||
throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
|
||||
}
|
||||
|
||||
function applyBindingsToDescendantsInternal(bindingContext, elementOrVirtualElement) {
|
||||
var currentChild, nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
|
||||
|
||||
|
|
@ -252,7 +231,7 @@ function applyBindingsToNodeAndDescendantsInternal(bindingContext, nodeVerified)
|
|||
// (1) We need to store the binding info for the node (all element nodes)
|
||||
// (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
|
||||
if (isElement || ko.bindingProvider.nodeHasBindings(nodeVerified))
|
||||
bindingContextForDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext)['bindingContextForDescendants'];
|
||||
bindingContextForDescendants = applyBindingsToNodeInternal(nodeVerified, null, bindingContext);
|
||||
|
||||
// Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
|
||||
// because it's unexpected and a potential XSS issue.
|
||||
|
|
@ -384,8 +363,9 @@ function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) {
|
|||
handlerUpdateFn = bindingKeyAndHandler.handler["update"],
|
||||
bindingKey = bindingKeyAndHandler.key;
|
||||
|
||||
if (node.nodeType === 8) {
|
||||
validateThatBindingIsAllowedForVirtualElements(bindingKey);
|
||||
// COMMENT_NODE
|
||||
if (node.nodeType === 8 && !ko.virtualElements.allowedBindings[bindingKey]) {
|
||||
throw Error("The binding '" + bindingKey + "' cannot be used with comment nodes");
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -397,7 +377,7 @@ function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) {
|
|||
// If this binding handler claims to control descendant bindings, make a note of this
|
||||
if (initResult && initResult['controlsDescendantBindings']) {
|
||||
if (bindingHandlerThatControlsDescendantBindings !== undefined)
|
||||
throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
|
||||
throw Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
|
||||
bindingHandlerThatControlsDescendantBindings = bindingKey;
|
||||
}
|
||||
});
|
||||
|
|
@ -417,11 +397,7 @@ function applyBindingsToNodeInternal(node, sourceBindings, bindingContext) {
|
|||
});
|
||||
}
|
||||
|
||||
var shouldBindDescendants = bindingHandlerThatControlsDescendantBindings === undefined;
|
||||
return {
|
||||
'shouldBindDescendants': shouldBindDescendants,
|
||||
'bindingContextForDescendants': shouldBindDescendants && contextToExtend
|
||||
};
|
||||
return bindingHandlerThatControlsDescendantBindings === undefined && contextToExtend;
|
||||
}
|
||||
|
||||
ko.storedBindingContextForNode = node => {
|
||||
|
|
@ -432,7 +408,7 @@ ko.storedBindingContextForNode = node => {
|
|||
function getBindingContext(viewModelOrBindingContext, extendContextCallback) {
|
||||
return viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
|
||||
? viewModelOrBindingContext
|
||||
: new ko.bindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback);
|
||||
: new ko.bindingContext(viewModelOrBindingContext, null, extendContextCallback);
|
||||
}
|
||||
|
||||
ko['applyBindingAccessorsToNode'] = (node, bindings, viewModelOrBindingContext) =>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,6 @@ ko.bindingHandlers['attr'] = {
|
|||
attrValue = attrValue.toString();
|
||||
namespace ? element.setAttributeNS(namespace, attrName, attrValue) : element.setAttribute(attrName, attrValue);
|
||||
}
|
||||
|
||||
// Treat "name" specially - although you can think of it as an attribute, it also needs
|
||||
// special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
|
||||
// Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
|
||||
// entirely, and there's no strong reason to allow for such casing in HTML.
|
||||
if (attrName === "name") {
|
||||
element.name = toRemove ? "" : attrValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ ko.bindingHandlers['checked'] = {
|
|||
// Treat "value" like "checkedValue" when it is included with "checked" binding
|
||||
if (allBindings['has']('checkedValue')) {
|
||||
return ko.utils.unwrapObservable(allBindings.get('checkedValue'));
|
||||
} else if (useElementValue) {
|
||||
}
|
||||
if (useElementValue) {
|
||||
return allBindings['has']('value')
|
||||
? ko.utils.unwrapObservable(allBindings.get('value'))
|
||||
: element.value;
|
||||
|
|
@ -78,10 +79,6 @@ ko.bindingHandlers['checked'] = {
|
|||
useElementValue = isRadio || valueIsArray,
|
||||
oldElemValue = valueIsArray ? checkedValue() : undefined;
|
||||
|
||||
// IE 6 won't allow radio buttons to be selected unless they have a name
|
||||
if (isRadio && !element.name)
|
||||
ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
|
||||
|
||||
// Set up two computeds to update the binding:
|
||||
|
||||
// The first responds to changes in the checkedValue value and to element clicks
|
||||
|
|
@ -113,7 +110,6 @@ ko.bindingHandlers['checked'] = {
|
|||
}
|
||||
}
|
||||
};
|
||||
//ko.expressionRewriting.twoWayBindings['checked'] = true;
|
||||
|
||||
ko.bindingHandlers['checkedValue'] = {
|
||||
'update'(element, valueAccessor) {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,28 @@
|
|||
// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
|
||||
// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
|
||||
// "foreach: { data: someExpression }" is equivalent to "template: { foreach: someExpression }"
|
||||
const makeTemplateValueAccessor = valueAccessor =>
|
||||
() => {
|
||||
var modelValue = valueAccessor(),
|
||||
// Unwrap without setting a dependency here
|
||||
unwrappedValue = ko.isObservable(modelValue) ? modelValue.peek() : modelValue;
|
||||
|
||||
// If unwrappedValue is the array, pass in the wrapped value on its own
|
||||
// The value will be unwrapped and tracked within the template binding
|
||||
// (See https://github.com/SteveSanderson/knockout/issues/523)
|
||||
if ((!unwrappedValue) || Array.isArray(unwrappedValue))
|
||||
return { 'foreach': modelValue };
|
||||
|
||||
// If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
|
||||
ko.utils.unwrapObservable(modelValue);
|
||||
return {
|
||||
'foreach': unwrappedValue['data']
|
||||
};
|
||||
};
|
||||
ko.bindingHandlers['foreach'] = {
|
||||
makeTemplateValueAccessor: valueAccessor =>
|
||||
() => {
|
||||
var modelValue = valueAccessor(),
|
||||
// Unwrap without setting a dependency here
|
||||
unwrappedValue = ko.isObservable(modelValue) ? modelValue.peek() : modelValue;
|
||||
|
||||
// If unwrappedValue is the array, pass in the wrapped value on its own
|
||||
// The value will be unwrapped and tracked within the template binding
|
||||
// (See https://github.com/SteveSanderson/knockout/issues/523)
|
||||
if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
|
||||
return { 'foreach': modelValue };
|
||||
|
||||
// If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
|
||||
ko.utils.unwrapObservable(modelValue);
|
||||
return {
|
||||
'foreach': unwrappedValue['data'],
|
||||
'as': unwrappedValue['as'],
|
||||
'beforeRemove': unwrappedValue['beforeRemove']
|
||||
};
|
||||
},
|
||||
'init': (element, valueAccessor) =>
|
||||
ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor))
|
||||
ko.bindingHandlers['template']['init'](element, makeTemplateValueAccessor(valueAccessor))
|
||||
,
|
||||
'update': (element, valueAccessor, allBindings, viewModel, bindingContext) =>
|
||||
ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext)
|
||||
ko.bindingHandlers['template']['update'](element, makeTemplateValueAccessor(valueAccessor), allBindings, viewModel, bindingContext)
|
||||
};
|
||||
ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
|
||||
ko.virtualElements.allowedBindings['foreach'] = true;
|
||||
|
|
|
|||
|
|
@ -36,4 +36,3 @@ ko.bindingHandlers['hasfocus'] = {
|
|||
}
|
||||
}
|
||||
};
|
||||
//ko.expressionRewriting.twoWayBindings.add('hasfocus');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function makeWithIfBinding(bindingKey, isWith, isNot) {
|
|||
var savedNodes, contextOptions = {}, needAsyncContext;
|
||||
|
||||
if (isWith) {
|
||||
contextOptions = { 'as': allBindings.get('as'), 'exportDependencies': true };
|
||||
contextOptions = { 'exportDependencies': true };
|
||||
}
|
||||
|
||||
needAsyncContext = allBindings['has'](ko.bindingEvent.descendantsComplete);
|
||||
|
|
@ -53,7 +53,6 @@ function makeWithIfBinding(bindingKey, isWith, isNot) {
|
|||
return { 'controlsDescendantBindings': true };
|
||||
}
|
||||
};
|
||||
ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
|
||||
ko.virtualElements.allowedBindings[bindingKey] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ var captionPlaceholder = {};
|
|||
ko.bindingHandlers['options'] = {
|
||||
'init': element => {
|
||||
if (!element.matches("SELECT"))
|
||||
throw new Error("options binding applies only to SELECT elements");
|
||||
throw Error("options binding applies only to SELECT elements");
|
||||
|
||||
// Remove all existing <option>s.
|
||||
let l = element.length;
|
||||
|
|
@ -19,7 +19,6 @@ ko.bindingHandlers['options'] = {
|
|||
multiple = element.multiple,
|
||||
previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null,
|
||||
unwrappedArray = ko.utils.unwrapObservable(valueAccessor()),
|
||||
valueAllowUnset = allBindings.get('valueAllowUnset') && allBindings['has']('value'),
|
||||
arrayToDomNodeChildrenOptions = {},
|
||||
captionValue,
|
||||
filteredArray,
|
||||
|
|
@ -38,10 +37,7 @@ ko.bindingHandlers['options'] = {
|
|||
},
|
||||
|
||||
setSelectionCallback = (arrayEntry, newOptions) => {
|
||||
if (itemUpdate && valueAllowUnset) {
|
||||
// The model value is authoritative, so make sure its value is the one selected
|
||||
ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete);
|
||||
} else if (previousSelectedValues.length) {
|
||||
if (previousSelectedValues.length) {
|
||||
// IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
|
||||
// That's why we first added them without selection. Now it's time to set the selection.
|
||||
var isSelected = previousSelectedValues.includes(ko.selectExtensions.readValue(newOptions[0]));
|
||||
|
|
@ -54,29 +50,18 @@ ko.bindingHandlers['options'] = {
|
|||
}
|
||||
};
|
||||
|
||||
if (!valueAllowUnset) {
|
||||
if (multiple) {
|
||||
previousSelectedValues = selectedOptions().map(ko.selectExtensions.readValue);
|
||||
} else if (element.selectedIndex >= 0) {
|
||||
previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
|
||||
}
|
||||
if (multiple) {
|
||||
previousSelectedValues = selectedOptions().map(ko.selectExtensions.readValue);
|
||||
} else if (element.selectedIndex >= 0) {
|
||||
previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex]));
|
||||
}
|
||||
|
||||
if (unwrappedArray) {
|
||||
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
|
||||
if (!Array.isArray(unwrappedArray)) // Coerce single value into array
|
||||
unwrappedArray = [unwrappedArray];
|
||||
|
||||
// Filter out any entries marked as destroyed
|
||||
filteredArray = unwrappedArray.filter(item => item || item == null);
|
||||
|
||||
// If caption is included, add it to the array
|
||||
if (allBindings['has']('optionsCaption')) {
|
||||
captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption'));
|
||||
// If caption value is null or undefined, don't show a caption
|
||||
if (captionValue != null) {
|
||||
filteredArray.unshift(captionPlaceholder);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If a falsy value is provided (e.g. null), we'll simply empty the select element
|
||||
}
|
||||
|
|
@ -88,12 +73,12 @@ ko.bindingHandlers['options'] = {
|
|||
var itemUpdate = false,
|
||||
optionForArrayItem = (arrayEntry, index, oldOptions) => {
|
||||
if (oldOptions.length) {
|
||||
previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
|
||||
previousSelectedValues = oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : [];
|
||||
itemUpdate = true;
|
||||
}
|
||||
var option = element.ownerDocument.createElement("option");
|
||||
if (arrayEntry === captionPlaceholder) {
|
||||
ko.utils.setTextContent(option, allBindings.get('optionsCaption'));
|
||||
ko.utils.setTextContent(option);
|
||||
ko.selectExtensions.writeValue(option, undefined);
|
||||
} else {
|
||||
// Apply a value to the option element
|
||||
|
|
@ -107,10 +92,6 @@ ko.bindingHandlers['options'] = {
|
|||
return [option];
|
||||
};
|
||||
|
||||
// By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection
|
||||
// problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208
|
||||
arrayToDomNodeChildrenOptions['beforeRemove'] = option => element.removeChild(option);
|
||||
|
||||
var callback = setSelectionCallback;
|
||||
if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") {
|
||||
callback = (arrayEntry, newOptions) => {
|
||||
|
|
@ -121,28 +102,26 @@ ko.bindingHandlers['options'] = {
|
|||
|
||||
ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback);
|
||||
|
||||
if (!valueAllowUnset) {
|
||||
// Determine if the selection has changed as a result of updating the options list
|
||||
var selectionChanged, prevLength = previousSelectedValues.length;
|
||||
if (multiple) {
|
||||
// For a multiple-select box, compare the new selection count to the previous one
|
||||
// But if nothing was selected before, the selection can't have changed
|
||||
selectionChanged = prevLength && selectedOptions().length < prevLength;
|
||||
} else {
|
||||
// For a single-select box, compare the current value to the previous value
|
||||
// But if nothing was selected before or nothing is selected now, just look for a change in selection
|
||||
selectionChanged = (prevLength && element.selectedIndex >= 0)
|
||||
? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
|
||||
: (prevLength || element.selectedIndex >= 0);
|
||||
}
|
||||
|
||||
// Ensure consistency between model value and selected option.
|
||||
// If the dropdown was changed so that selection is no longer the same,
|
||||
// notify the value or selectedOptions binding.
|
||||
selectionChanged && ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
||||
// Determine if the selection has changed as a result of updating the options list
|
||||
var selectionChanged, prevLength = previousSelectedValues.length;
|
||||
if (multiple) {
|
||||
// For a multiple-select box, compare the new selection count to the previous one
|
||||
// But if nothing was selected before, the selection can't have changed
|
||||
selectionChanged = prevLength && selectedOptions().length < prevLength;
|
||||
} else {
|
||||
// For a single-select box, compare the current value to the previous value
|
||||
// But if nothing was selected before or nothing is selected now, just look for a change in selection
|
||||
selectionChanged = (prevLength && element.selectedIndex >= 0)
|
||||
? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0])
|
||||
: (prevLength || element.selectedIndex >= 0);
|
||||
}
|
||||
|
||||
if (valueAllowUnset || ko.dependencyDetection.isInitial()) {
|
||||
// Ensure consistency between model value and selected option.
|
||||
// If the dropdown was changed so that selection is no longer the same,
|
||||
// notify the value or selectedOptions binding.
|
||||
selectionChanged && ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
|
||||
|
||||
if (ko.dependencyDetection.isInitial()) {
|
||||
ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
ko.bindingHandlers['submit'] = {
|
||||
'init': (element, valueAccessor, allBindings, viewModel, bindingContext) => {
|
||||
if (typeof valueAccessor() != "function")
|
||||
throw new Error("The value for a submit binding must be a function");
|
||||
throw Error("The value for a submit binding must be a function");
|
||||
element.addEventListener("submit", event => {
|
||||
var handlerReturnValue;
|
||||
var value = valueAccessor();
|
||||
|
|
|
|||
|
|
@ -45,16 +45,6 @@ ko.bindingHandlers['textInput'] = {
|
|||
// Bind to the change event so that we can catch programmatic updates of the value that fire this event.
|
||||
onEvent('change', updateModel);
|
||||
|
||||
// To deal with browsers that don't notify any kind of event for some changes (IE, Safari, etc.)
|
||||
onEvent('blur', updateModel);
|
||||
|
||||
ko.computed(updateView, { disposeWhenNodeIsRemoved: element });
|
||||
}
|
||||
};
|
||||
//ko.expressionRewriting.twoWayBindings.add('textInput');
|
||||
|
||||
// textinput is an alias for textInput
|
||||
ko.bindingHandlers['textinput'] = {
|
||||
// preprocess is the only way to set up a full alias
|
||||
'preprocess': (value, name, addBinding) => addBinding('textInput', value)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79,9 +79,8 @@ ko.bindingHandlers['value'] = {
|
|||
|
||||
if (valueHasChanged || elementValue === undefined) {
|
||||
if (isSelectElement) {
|
||||
var allowUnset = allBindings.get('valueAllowUnset');
|
||||
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
||||
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
|
||||
ko.selectExtensions.writeValue(element, newValue);
|
||||
if (newValue !== ko.selectExtensions.readValue(element)) {
|
||||
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
||||
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
||||
ko.dependencyDetection.ignore(valueUpdateHandler);
|
||||
|
|
@ -112,4 +111,3 @@ ko.bindingHandlers['value'] = {
|
|||
},
|
||||
'update': () => {} // Keep for backwards compatibility with code that may have wrapped value binding
|
||||
};
|
||||
//ko.expressionRewriting.twoWayBindings.add('value');
|
||||
|
|
|
|||
|
|
@ -32,8 +32,14 @@
|
|||
// of which nodes would be deleted if valueToMap was itself later removed
|
||||
mappedNodes.length = 0;
|
||||
mappedNodes.push(...newMappedNodes);
|
||||
}, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: ()=>!!mappedNodes.find(ko.utils.domNodeIsAttachedToDocument) });
|
||||
return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
|
||||
}, {
|
||||
disposeWhenNodeIsRemoved: containerNode,
|
||||
disposeWhen: ()=>!!mappedNodes.find(ko.utils.domNodeIsAttachedToDocument)
|
||||
});
|
||||
return {
|
||||
mappedNodes : mappedNodes,
|
||||
dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined)
|
||||
};
|
||||
}
|
||||
|
||||
var lastMappingResultDomDataKey = ko.utils.domData.nextKey(),
|
||||
|
|
@ -41,7 +47,7 @@
|
|||
|
||||
ko.utils.setDomNodeChildrenFromArrayMapping = (domNode, array, mapping, options, callbackAfterAddingNodes, editScript) => {
|
||||
array = array || [];
|
||||
if (typeof array.length == "undefined") // Coerce single value into array
|
||||
if (!Array.isArray(array)) // Coerce single value into array
|
||||
array = [array];
|
||||
|
||||
options = options || {};
|
||||
|
|
@ -70,12 +76,6 @@
|
|||
mapData.indexObservable(currentArrayIndex++);
|
||||
ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
|
||||
newMappingResult.push(mapData);
|
||||
},
|
||||
|
||||
callCallback = (callback, items) => {
|
||||
if (callback) {
|
||||
items.forEach(item => item?.mappedNodes.forEach(node => callback(node, i, item.arrayEntry)));
|
||||
}
|
||||
};
|
||||
|
||||
if (isFirstExecution) {
|
||||
|
|
@ -87,7 +87,6 @@
|
|||
Array.prototype.map.call(lastMappingResult, x => x.arrayEntry),
|
||||
array,
|
||||
{
|
||||
'dontLimitMoves': options['dontLimitMoves'],
|
||||
'sparse': true
|
||||
});
|
||||
}
|
||||
|
|
@ -112,15 +111,6 @@
|
|||
|
||||
// Queue these nodes for later removal
|
||||
if (ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
|
||||
if (options['beforeRemove']) {
|
||||
newMappingResult.push(mapData);
|
||||
countWaitingForRemove++;
|
||||
if (mapData.arrayEntry === deletedItemDummyValue) {
|
||||
mapData = null;
|
||||
} else {
|
||||
itemsForBeforeRemoveCallbacks[mapData.indexObservable.peek()] = mapData;
|
||||
}
|
||||
}
|
||||
if (mapData) {
|
||||
nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
|
||||
}
|
||||
|
|
@ -155,8 +145,8 @@
|
|||
// Store a copy of the array items we just considered so we can difference it next time
|
||||
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
|
||||
|
||||
// Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
|
||||
nodesToDelete.forEach(options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
|
||||
// Next remove nodes for deleted items
|
||||
nodesToDelete.forEach(ko.removeNode);
|
||||
|
||||
var i, lastNode, mappedNodes, activeElement,
|
||||
insertNode = nodeToInsert => {
|
||||
|
|
@ -183,7 +173,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
|
||||
// Next add/reorder the remaining items
|
||||
newMappingResult.forEach(mapData => {
|
||||
// Get nodes for newly added items
|
||||
mapData.mappedNodes
|
||||
|
|
@ -205,15 +195,8 @@
|
|||
activeElement?.focus();
|
||||
}
|
||||
|
||||
// If there's a beforeRemove callback, call it after reordering.
|
||||
// Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
|
||||
// some sort of animation, which is why we first reorder the nodes that will be removed. If the
|
||||
// callback instead removes the nodes right away, it would be more efficient to skip reordering them.
|
||||
// Perhaps we'll make that change in the future if this scenario becomes more common.
|
||||
callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
|
||||
|
||||
// Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
|
||||
// as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
|
||||
// as already "removed", and it ensures that the item won't match up
|
||||
// with an actual item in the array and appear as "retained" or "moved".
|
||||
itemsForBeforeRemoveCallbacks.forEach(callback => callback && (callback.arrayEntry = deletedItemDummyValue));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,16 +78,13 @@ ko.utils.compareArrays = (() => {
|
|||
|
||||
// Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
|
||||
// smlIndexMax keeps the time complexity of this algorithm linear.
|
||||
ko.utils.findMovesInArrayComparison(notInBig, notInSml, !options['dontLimitMoves'] && smlIndexMax * 10);
|
||||
ko.utils.findMovesInArrayComparison(notInBig, notInSml, smlIndexMax * 10);
|
||||
|
||||
return editScript.reverse();
|
||||
};
|
||||
|
||||
// Simple calculation based on Levenshtein distance.
|
||||
return (oldArray, newArray, options) => {
|
||||
// For backward compatibility, if the third arg is actually a bool, interpret
|
||||
// it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
|
||||
options = (typeof options === 'boolean') ? { 'dontLimitMoves': options } : (options || {});
|
||||
oldArray = oldArray || [];
|
||||
newArray = newArray || [];
|
||||
|
||||
|
|
|
|||
|
|
@ -112,9 +112,6 @@ ko.expressionRewriting = (() => {
|
|||
return result;
|
||||
},
|
||||
|
||||
// Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
|
||||
// twoWayBindings = new Set,
|
||||
|
||||
preProcessBindings = (bindingsStringOrKeyValueArray) => {
|
||||
|
||||
var resultStrings = [],
|
||||
|
|
@ -122,16 +119,6 @@ ko.expressionRewriting = (() => {
|
|||
keyValueArray = parseObjectLiteral(bindingsStringOrKeyValueArray),
|
||||
|
||||
processKeyValue = (key, val) => {
|
||||
var /*writableVal,*/ obj = ko.bindingHandlers[key];
|
||||
if (obj?.['preprocess'] && !obj['preprocess'](val, key, processKeyValue))
|
||||
return;
|
||||
/*
|
||||
if (twoWayBindings.has(key) && (writableVal = getWriteableValue(val))) {
|
||||
// For two-way bindings, provide a write method in case the value
|
||||
// isn't a writable observable.
|
||||
propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
|
||||
}
|
||||
*/
|
||||
// Values are wrapped in a function so that each value can be accessed independently
|
||||
val = 'function(){return ' + val + ' }';
|
||||
resultStrings.push("'" + key + "':" + val);
|
||||
|
|
@ -148,10 +135,6 @@ ko.expressionRewriting = (() => {
|
|||
};
|
||||
|
||||
return {
|
||||
bindingRewriteValidators: [],
|
||||
|
||||
// twoWayBindings: twoWayBindings,
|
||||
|
||||
parseObjectLiteral: parseObjectLiteral,
|
||||
|
||||
preProcessBindings: preProcessBindings,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ ko.selectExtensions = {
|
|||
}
|
||||
},
|
||||
|
||||
writeValue: (element, value, allowUnset) => {
|
||||
writeValue: (element, value) => {
|
||||
switch (element.nodeName) {
|
||||
case 'OPTION':
|
||||
if (typeof value === "string") {
|
||||
|
|
@ -49,7 +49,7 @@ ko.selectExtensions = {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (allowUnset || selection >= 0 || (noValue && element.size > 1)) {
|
||||
if (selection >= 0 || (noValue && element.size > 1)) {
|
||||
element.selectedIndex = selection;
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue