Bugfix: compose mail select contacts for cc/bcc failed

Cleanup: Inputosaurus and Knockout
Change: Knockout domData now uses WeakMap
Replaced: Knockout domManipulation with a documentFragment
This commit is contained in:
djmaze 2021-02-01 14:34:24 +01:00
parent fe7c7fede4
commit ebe2c0536f
23 changed files with 410 additions and 889 deletions

View file

@ -7,109 +7,52 @@
// 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
// without reading/writing the actual element text content, since it will be overwritten
// with the rendered template output.
// You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
// Template sources need to have the following functions:
// text() - returns the template text from your storage location
// text(value) - writes the supplied template text to your storage location
// data(key) - reads values stored using data(key, value) - see below
// data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
//
// Optionally, template sources can also have the following functions:
// nodes() - returns a DOM element containing the nodes of this template, where available
// nodes(value) - writes the given DOM element to your storage location
// If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
// for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
//
// Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
// using and overriding "makeTemplateSource" to return an instance of your custom template source.
ko.templateSources = {};
// ---- ko.templateSources.domElement -----
// template types
var templateTemplate = 3,
templateElement = 4;
ko.templateSources.domElement = function(element) {
this.domElement = element;
if (element) {
this.templateType =
element.matches("TEMPLATE") && element.content && element.content.nodeType === 11 ? templateTemplate :
templateElement;
}
}
ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
var elemContentsProperty = "innerHTML";
if (arguments.length == 0) {
return this.domElement.innerHTML;
}
ko.utils.setHtml(this.domElement, arguments[0]);
};
var dataDomDataPrefix = ko.utils.domData.nextKey() + "_";
ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
if (arguments.length === 1) {
return ko.utils.domData.get(this.domElement, dataDomDataPrefix + key);
}
ko.utils.domData.set(this.domElement, dataDomDataPrefix + key, arguments[1]);
};
var templatesDomDataKey = ko.utils.domData.nextKey();
function getTemplateDomData(element) {
return ko.utils.domData.get(element, templatesDomDataKey) || {};
}
function setTemplateDomData(element, data) {
ko.utils.domData.set(element, templatesDomDataKey, data);
}
ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
var element = this.domElement;
if (arguments.length == 0) {
var templateData = getTemplateDomData(element),
nodes = templateData.containerData || (
this.templateType === templateTemplate ? element.content :
this.templateType === templateElement ? element :
undefined);
if (!nodes || templateData.alwaysCheckText) {
// If the template is associated with an element that stores the template as text,
// parse and cache the nodes whenever there's new text content available. This allows
// the user to update the template content by updating the text of template node.
var text = this['text']();
if (text && text !== templateData.textData) {
nodes = ko.utils.parseHtmlForTemplateNodes(text, element.ownerDocument);
setTemplateDomData(element, {containerData: nodes, textData: text, alwaysCheckText: true});
}
}
return nodes;
}
var valueToWrite = arguments[0];
if (this.templateType !== undefined) {
this['text'](""); // clear the text from the node
}
setTemplateDomData(element, {containerData: valueToWrite});
};
let templatesDomDataKey = ko.utils.domData.nextKey();
// ---- ko.templateSources.anonymousTemplate -----
// Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
// For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
// Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
ko.templateSources.anonymousTemplate = function(element) {
this.domElement = element;
};
ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
ko.templateSources.anonymousTemplate.prototype.constructor = ko.templateSources.anonymousTemplate;
ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
if (arguments.length == 0) {
var templateData = getTemplateDomData(this.domElement);
if (templateData.textData === undefined && templateData.containerData)
templateData.textData = templateData.containerData.innerHTML;
return templateData.textData;
class anonymousTemplate
{
constructor(element)
{
this.domElement = element;
}
var valueToWrite = arguments[0];
setTemplateDomData(this.domElement, {textData: valueToWrite});
nodes(...args)
{
let element = this.domElement;
if (!args.length) {
return ko.utils.domData.get(element, templatesDomDataKey) || (
this.templateType === 11 ? element.content :
this.templateType === 1 ? element :
undefined);
}
ko.utils.domData.set(element, templatesDomDataKey, args[0]);
}
}
// ---- ko.templateSources.domElement -----
class domElement extends anonymousTemplate
{
constructor(element)
{
super(element);
if (element) {
this.templateType =
element.matches("TEMPLATE") && element.content ? element.content.nodeType : 1;
}
}
}
ko.templateSources = {
domElement: domElement,
anonymousTemplate: anonymousTemplate
};
})();

View file

@ -1,9 +1,9 @@
(() => {
var renderTemplateSource = (templateSource, bindingContext, options, templateDocument) => {
var renderTemplateSource = templateSource => {
var templateNodes = templateSource.nodes ? templateSource.nodes() : null;
return templateNodes
? [...templateNodes.cloneNode(true).childNodes]
: ko.utils.parseHtmlFragment(templateSource['text'](), templateDocument);
: null;
},
makeTemplateSource = (template, templateDocument) => {
@ -40,36 +40,7 @@
if (continuousNodeArray.length) {
var firstNode = continuousNodeArray[0],
lastNode = continuousNodeArray[continuousNodeArray.length - 1],
parentNode = firstNode.parentNode,
provider = ko.bindingProvider['instance'],
preprocessNode = provider['preprocessNode'];
if (preprocessNode) {
invokeForEachNodeInContinuousRange(firstNode, lastNode, (node, nextNodeInRange) => {
var nodePreviousSibling = node.previousSibling;
var newNodes = preprocessNode.call(provider, node);
if (newNodes) {
if (node === firstNode)
firstNode = newNodes[0] || nextNodeInRange;
if (node === lastNode)
lastNode = newNodes[newNodes.length - 1] || nodePreviousSibling;
}
});
// Because preprocessNode can change the nodes, including the first and last nodes, update continuousNodeArray to match.
// We need the full set, including inner nodes, because the unmemoize step might remove the first node (and so the real
// first node needs to be in the array).
continuousNodeArray.length = 0;
if (!firstNode) { // preprocessNode might have removed all the nodes, in which case there's nothing left to do
return;
}
if (firstNode === lastNode) {
continuousNodeArray.push(firstNode);
} else {
continuousNodeArray.push(firstNode, lastNode);
ko.utils.fixUpContinuousNodeArray(continuousNodeArray, parentNode);
}
}
parentNode = firstNode.parentNode;
// Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
// whereas a regular applyBindings won't introduce new memoized nodes
@ -94,10 +65,7 @@
var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
var templateDocument = (firstTargetNode || template || {}).ownerDocument;
var renderedNodesArray = renderTemplateSource(
makeTemplateSource(template, templateDocument),
bindingContext, options, templateDocument
);
var renderedNodesArray = renderTemplateSource(makeTemplateSource(template, templateDocument));
// Loosely check result is an array of DOM nodes
if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
@ -266,13 +234,13 @@
ko.utils.domData.set(container, cleanContainerDomDataKey, true);
}
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
new ko.templateSources.anonymousTemplate(element).nodes(container);
} else {
// It's an anonymous template - store the element contents, then clear the element
var templateNodes = ko.virtualElements.childNodes(element);
if (templateNodes.length > 0) {
let container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
new ko.templateSources.anonymousTemplate(element).nodes(container);
} else {
throw new Error("Anonymous template defined, but no template content was provided");
}