mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-09 06:28:28 +03:00
183 lines
7.9 KiB
JavaScript
183 lines
7.9 KiB
JavaScript
(function() {
|
|
// "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
|
|
// may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
|
|
// If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
|
|
// of that virtual hierarchy
|
|
//
|
|
// The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
|
|
// without having to scatter special cases all over the binding and templating code.
|
|
|
|
var startCommentRegex = /^\s*ko(?:\s+([\s\S]+))?\s*$/;
|
|
var endCommentRegex = /^\s*\/ko\s*$/;
|
|
var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
|
|
|
|
function isStartComment(node) {
|
|
return (node.nodeType == 8) && startCommentRegex.test(node.nodeValue);
|
|
}
|
|
|
|
function isEndComment(node) {
|
|
return (node.nodeType == 8) && endCommentRegex.test(node.nodeValue);
|
|
}
|
|
|
|
function isUnmatchedEndComment(node) {
|
|
return isEndComment(node) && !(ko.utils.domData.get(node, matchedEndCommentDataKey));
|
|
}
|
|
|
|
var matchedEndCommentDataKey = "__ko_matchedEndComment__"
|
|
|
|
function getVirtualChildren(startComment, allowUnbalanced) {
|
|
var currentNode = startComment;
|
|
var depth = 1;
|
|
var children = [];
|
|
while (currentNode = currentNode.nextSibling) {
|
|
if (isEndComment(currentNode)) {
|
|
ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true);
|
|
depth--;
|
|
if (depth === 0)
|
|
return children;
|
|
}
|
|
|
|
children.push(currentNode);
|
|
|
|
if (isStartComment(currentNode))
|
|
depth++;
|
|
}
|
|
if (!allowUnbalanced)
|
|
throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
|
|
return null;
|
|
}
|
|
|
|
function getMatchingEndComment(startComment, allowUnbalanced) {
|
|
var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
|
|
if (allVirtualChildren) {
|
|
if (allVirtualChildren.length > 0)
|
|
return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
|
|
return startComment.nextSibling;
|
|
} else
|
|
return null; // Must have no matching end comment, and allowUnbalanced is true
|
|
}
|
|
|
|
function getUnbalancedChildTags(node) {
|
|
// e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
|
|
// from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
|
|
var childNode = node.firstChild, captureRemaining = null;
|
|
if (childNode) {
|
|
do {
|
|
if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
|
|
captureRemaining.push(childNode);
|
|
else if (isStartComment(childNode)) {
|
|
var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
|
|
if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
|
|
childNode = matchingEndComment;
|
|
else
|
|
captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
|
|
} else if (isEndComment(childNode)) {
|
|
captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
|
|
}
|
|
} while (childNode = childNode.nextSibling);
|
|
}
|
|
return captureRemaining;
|
|
}
|
|
|
|
ko.virtualElements = {
|
|
allowedBindings: {},
|
|
|
|
childNodes: function(node) {
|
|
return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
|
|
},
|
|
|
|
emptyNode: function(node) {
|
|
if (!isStartComment(node))
|
|
ko.utils.emptyDomNode(node);
|
|
else {
|
|
var virtualChildren = ko.virtualElements.childNodes(node);
|
|
for (var i = 0, j = virtualChildren.length; i < j; i++)
|
|
ko.removeNode(virtualChildren[i]);
|
|
}
|
|
},
|
|
|
|
setDomNodeChildren: function(node, childNodes) {
|
|
if (!isStartComment(node))
|
|
ko.utils.setDomNodeChildren(node, childNodes);
|
|
else {
|
|
ko.virtualElements.emptyNode(node);
|
|
var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
|
|
for (var i = 0, j = childNodes.length; i < j; i++)
|
|
endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
|
|
}
|
|
},
|
|
|
|
prepend: function(containerNode, nodeToPrepend) {
|
|
var insertBeforeNode;
|
|
|
|
if (isStartComment(containerNode)) {
|
|
// Start comments must always have a parent and at least one following sibling (the end comment)
|
|
insertBeforeNode = containerNode.nextSibling;
|
|
containerNode = containerNode.parentNode;
|
|
} else {
|
|
insertBeforeNode = containerNode.firstChild;
|
|
}
|
|
|
|
containerNode.insertBefore(nodeToPrepend, insertBeforeNode);
|
|
},
|
|
|
|
insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
|
|
if (!insertAfterNode) {
|
|
ko.virtualElements.prepend(containerNode, nodeToInsert);
|
|
} else {
|
|
// Children of start comments must always have a parent and at least one following sibling (the end comment)
|
|
var insertBeforeNode = insertAfterNode.nextSibling;
|
|
|
|
if (isStartComment(containerNode)) {
|
|
containerNode = containerNode.parentNode;
|
|
}
|
|
|
|
containerNode.insertBefore(nodeToInsert, insertBeforeNode);
|
|
}
|
|
},
|
|
|
|
firstChild: function(node) {
|
|
if (!isStartComment(node)) {
|
|
if (node.firstChild && isEndComment(node.firstChild)) {
|
|
throw new Error("Found invalid end comment, as the first child of " + node);
|
|
}
|
|
return node.firstChild;
|
|
} else if (!node.nextSibling || isEndComment(node.nextSibling)) {
|
|
return null;
|
|
} else {
|
|
return node.nextSibling;
|
|
}
|
|
},
|
|
|
|
nextSibling: function(node) {
|
|
if (isStartComment(node)) {
|
|
node = getMatchingEndComment(node);
|
|
}
|
|
|
|
if (node.nextSibling && isEndComment(node.nextSibling)) {
|
|
if (isUnmatchedEndComment(node.nextSibling)) {
|
|
throw Error("Found end comment without a matching opening comment, as child of " + node);
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return node.nextSibling;
|
|
}
|
|
},
|
|
|
|
hasBindingValue: isStartComment,
|
|
|
|
virtualNodeBindingValue: function(node) {
|
|
var regexMatch = (node.nodeValue).match(startCommentRegex);
|
|
return regexMatch ? regexMatch[1] : null;
|
|
}
|
|
};
|
|
})();
|
|
ko.exportSymbol('virtualElements', ko.virtualElements);
|
|
ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
|
|
ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
|
|
//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
|
|
ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
|
|
//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
|
|
ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
|
|
ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
|