/** * @license Rangy, a cross-browser JavaScript range and selection library * http://code.google.com/p/rangy/ * * Copyright 2012, Tim Down * Licensed under the MIT license. * Version: 1.2.3 * Build date: 26 February 2012 */ window['rangy'] = (function() { var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"]; var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; // Subset of TextRange's full set of methods that we're interested in var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"]; /*----------------------------------------------------------------------------------------------------------------*/ // Trio of functions taken from Peter Michaux's article: // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting function isHostMethod(o, p) { var t = typeof o[p]; return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; } function isHostObject(o, p) { return !!(typeof o[p] == OBJECT && o[p]); } function isHostProperty(o, p) { return typeof o[p] != UNDEFINED; } // Creates a convenience function to save verbose repeated calls to tests functions function createMultiplePropertyTest(testFunc) { return function(o, props) { var i = props.length; while (i--) { if (!testFunc(o, props[i])) { return false; } } return true; }; } // Next trio of functions are a convenience to save verbose repeated calls to previous two functions var areHostMethods = createMultiplePropertyTest(isHostMethod); var areHostObjects = createMultiplePropertyTest(isHostObject); var areHostProperties = createMultiplePropertyTest(isHostProperty); function isTextRange(range) { return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); } var api = { version: "1.2.3", initialized: false, supported: true, util: { isHostMethod: isHostMethod, isHostObject: isHostObject, isHostProperty: isHostProperty, areHostMethods: areHostMethods, areHostObjects: areHostObjects, areHostProperties: areHostProperties, isTextRange: isTextRange }, features: {}, modules: {}, config: { alertOnWarn: false, preferTextRange: false } }; function fail(reason) { window.alert("Rangy not supported in your browser. Reason: " + reason); api.initialized = true; api.supported = false; } api.fail = fail; function warn(msg) { var warningMessage = "Rangy warning: " + msg; if (api.config.alertOnWarn) { window.alert(warningMessage); } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) { window.console.log(warningMessage); } } api.warn = warn; if ({}.hasOwnProperty) { api.util.extend = function(o, props) { for (var i in props) { if (props.hasOwnProperty(i)) { o[i] = props[i]; } } }; } else { fail("hasOwnProperty not supported"); } var initListeners = []; var moduleInitializers = []; // Initialization function init() { if (api.initialized) { return; } var testRange; var implementsDomRange = false, implementsTextRange = false; // First, perform basic feature tests if (isHostMethod(document, "createRange")) { testRange = document.createRange(); if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { implementsDomRange = true; } testRange.detach(); } var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0]; if (body && isHostMethod(body, "createTextRange")) { testRange = body.createTextRange(); if (isTextRange(testRange)) { implementsTextRange = true; } } if (!implementsDomRange && !implementsTextRange) { fail("Neither Range nor TextRange are implemented"); } api.initialized = true; api.features = { implementsDomRange: implementsDomRange, implementsTextRange: implementsTextRange }; // Initialize modules and call init listeners var allListeners = moduleInitializers.concat(initListeners); for (var i = 0, len = allListeners.length; i < len; ++i) { try { allListeners[i](api); } catch (ex) { if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { window.console.log("Init listener threw an exception. Continuing.", ex); } } } } // Allow external scripts to initialize this library in case it's loaded after the document has loaded api.init = init; // Execute listener immediately if already initialized api.addInitListener = function(listener) { if (api.initialized) { listener(api); } else { initListeners.push(listener); } }; var createMissingNativeApiListeners = []; api.addCreateMissingNativeApiListener = function(listener) { createMissingNativeApiListeners.push(listener); }; function createMissingNativeApi(win) { win = win || window; init(); // Notify listeners for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { createMissingNativeApiListeners[i](win); } } api.createMissingNativeApi = createMissingNativeApi; /** * @constructor */ function Module(name) { this.name = name; this.initialized = false; this.supported = false; } Module.prototype.fail = function(reason) { this.initialized = true; this.supported = false; throw new Error("Module '" + this.name + "' failed to load: " + reason); }; Module.prototype.warn = function(msg) { api.warn("Module " + this.name + ": " + msg); }; Module.prototype.createError = function(msg) { return new Error("Error in Rangy " + this.name + " module: " + msg); }; api.createModule = function(name, initFunc) { var module = new Module(name); api.modules[name] = module; moduleInitializers.push(function(api) { initFunc(api, module); module.initialized = true; module.supported = true; }); }; api.requireModules = function(modules) { for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) { moduleName = modules[i]; module = api.modules[moduleName]; if (!module || !(module instanceof Module)) { throw new Error("Module '" + moduleName + "' not found"); } if (!module.supported) { throw new Error("Module '" + moduleName + "' not supported"); } } }; /*----------------------------------------------------------------------------------------------------------------*/ // Wait for document to load before running tests var docReady = false; var loadHandler = function(e) { if (!docReady) { docReady = true; if (!api.initialized) { init(); } } }; // Test whether we have window and document objects that we will need if (typeof window == UNDEFINED) { fail("No window found"); return; } if (typeof document == UNDEFINED) { fail("No document found"); return; } if (isHostMethod(document, "addEventListener")) { document.addEventListener("DOMContentLoaded", loadHandler, false); } // Add a fallback in case the DOMContentLoaded event isn't supported if (isHostMethod(window, "addEventListener")) { window.addEventListener("load", loadHandler, false); } else if (isHostMethod(window, "attachEvent")) { window.attachEvent("onload", loadHandler); } else { fail("Window does not have required addEventListener or attachEvent method"); } return api; })(); rangy.createModule("DomUtil", function(api, module) { var UNDEF = "undefined"; var util = api.util; // Perform feature tests if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { module.fail("document missing a Node creation method"); } if (!util.isHostMethod(document, "getElementsByTagName")) { module.fail("document missing getElementsByTagName method"); } var el = document.createElement("div"); if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { module.fail("Incomplete Element implementation"); } // innerHTML is required for Range's createContextualFragment method if (!util.isHostProperty(el, "innerHTML")) { module.fail("Element is missing innerHTML property"); } var textNode = document.createTextNode("test"); if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || !util.areHostProperties(textNode, ["data"]))) { module.fail("Incomplete Text Node implementation"); } /*----------------------------------------------------------------------------------------------------------------*/ // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that // contains just the document as a single element and the value searched for is the document. var arrayContains = /*Array.prototype.indexOf ? function(arr, val) { return arr.indexOf(val) > -1; }:*/ function(arr, val) { var i = arr.length; while (i--) { if (arr[i] === val) { return true; } } return false; }; // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI function isHtmlNamespace(node) { var ns; return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); } function parentElement(node) { var parent = node.parentNode; return (parent.nodeType == 1) ? parent : null; } function getNodeIndex(node) { var i = 0; while( (node = node.previousSibling) ) { i++; } return i; } function getNodeLength(node) { var childNodes; return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0); } function getCommonAncestor(node1, node2) { var ancestors = [], n; for (n = node1; n; n = n.parentNode) { ancestors.push(n); } for (n = node2; n; n = n.parentNode) { if (arrayContains(ancestors, n)) { return n; } } return null; } function isAncestorOf(ancestor, descendant, selfIsAncestor) { var n = selfIsAncestor ? descendant : descendant.parentNode; while (n) { if (n === ancestor) { return true; } else { n = n.parentNode; } } return false; } function getClosestAncestorIn(node, ancestor, selfIsAncestor) { var p, n = selfIsAncestor ? node : node.parentNode; while (n) { p = n.parentNode; if (p === ancestor) { return n; } n = p; } return null; } function isCharacterDataNode(node) { var t = node.nodeType; return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment } function insertAfter(node, precedingNode) { var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; if (nextNode) { parent.insertBefore(node, nextNode); } else { parent.appendChild(node); } return node; } // Note that we cannot use splitText() because it is bugridden in IE 9. function splitDataNode(node, index) { var newNode = node.cloneNode(false); newNode.deleteData(0, index); node.deleteData(index, node.length - index); insertAfter(newNode, node); return newNode; } function getDocument(node) { if (node.nodeType == 9) { return node; } else if (typeof node.ownerDocument != UNDEF) { return node.ownerDocument; } else if (typeof node.document != UNDEF) { return node.document; } else if (node.parentNode) { return getDocument(node.parentNode); } else { throw new Error("getDocument: no document found for node"); } } function getWindow(node) { var doc = getDocument(node); if (typeof doc.defaultView != UNDEF) { return doc.defaultView; } else if (typeof doc.parentWindow != UNDEF) { return doc.parentWindow; } else { throw new Error("Cannot get a window object for node"); } } function getIframeDocument(iframeEl) { if (typeof iframeEl.contentDocument != UNDEF) { return iframeEl.contentDocument; } else if (typeof iframeEl.contentWindow != UNDEF) { return iframeEl.contentWindow.document; } else { throw new Error("getIframeWindow: No Document object found for iframe element"); } } function getIframeWindow(iframeEl) { if (typeof iframeEl.contentWindow != UNDEF) { return iframeEl.contentWindow; } else if (typeof iframeEl.contentDocument != UNDEF) { return iframeEl.contentDocument.defaultView; } else { throw new Error("getIframeWindow: No Window object found for iframe element"); } } function getBody(doc) { return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; } function getRootContainer(node) { var parent; while ( (parent = node.parentNode) ) { node = parent; } return node; } function comparePoints(nodeA, offsetA, nodeB, offsetB) { // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing var nodeC, root, childA, childB, n; if (nodeA == nodeB) { // Case 1: nodes are the same return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { // Case 2: node C (container B or an ancestor) is a child node of A return offsetA <= getNodeIndex(nodeC) ? -1 : 1; } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { // Case 3: node C (container A or an ancestor) is a child node of B return getNodeIndex(nodeC) < offsetB ? -1 : 1; } else { // Case 4: containers are siblings or descendants of siblings root = getCommonAncestor(nodeA, nodeB); childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); if (childA === childB) { // This shouldn't be possible throw new Error("comparePoints got to case 4 and childA and childB are the same!"); } else { n = root.firstChild; while (n) { if (n === childA) { return -1; } else if (n === childB) { return 1; } n = n.nextSibling; } throw new Error("Should not be here!"); } } } function fragmentFromNodeChildren(node) { var fragment = getDocument(node).createDocumentFragment(), child; while ( (child = node.firstChild) ) { fragment.appendChild(child); } return fragment; } function inspectNode(node) { if (!node) { return "[No node]"; } if (isCharacterDataNode(node)) { return '"' + node.data + '"'; } else if (node.nodeType == 1) { var idAttr = node.id ? ' id="' + node.id + '"' : ""; return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]"; } else { return node.nodeName; } } /** * @constructor */ function NodeIterator(root) { this.root = root; this._next = root; } NodeIterator.prototype = { _current: null, hasNext: function() { return !!this._next; }, next: function() { var n = this._current = this._next; var child, next; if (this._current) { child = n.firstChild; if (child) { this._next = child; } else { next = null; while ((n !== this.root) && !(next = n.nextSibling)) { n = n.parentNode; } this._next = next; } } return this._current; }, detach: function() { this._current = this._next = this.root = null; } }; function createIterator(root) { return new NodeIterator(root); } /** * @constructor */ function DomPosition(node, offset) { this.node = node; this.offset = offset; } DomPosition.prototype = { equals: function(pos) { return this.node === pos.node & this.offset == pos.offset; }, inspect: function() { return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; } }; /** * @constructor */ function DOMException(codeName) { this.code = this[codeName]; this.codeName = codeName; this.message = "DOMException: " + this.codeName; } DOMException.prototype = { INDEX_SIZE_ERR: 1, HIERARCHY_REQUEST_ERR: 3, WRONG_DOCUMENT_ERR: 4, NO_MODIFICATION_ALLOWED_ERR: 7, NOT_FOUND_ERR: 8, NOT_SUPPORTED_ERR: 9, INVALID_STATE_ERR: 11 }; DOMException.prototype.toString = function() { return this.message; }; api.dom = { arrayContains: arrayContains, isHtmlNamespace: isHtmlNamespace, parentElement: parentElement, getNodeIndex: getNodeIndex, getNodeLength: getNodeLength, getCommonAncestor: getCommonAncestor, isAncestorOf: isAncestorOf, getClosestAncestorIn: getClosestAncestorIn, isCharacterDataNode: isCharacterDataNode, insertAfter: insertAfter, splitDataNode: splitDataNode, getDocument: getDocument, getWindow: getWindow, getIframeWindow: getIframeWindow, getIframeDocument: getIframeDocument, getBody: getBody, getRootContainer: getRootContainer, comparePoints: comparePoints, inspectNode: inspectNode, fragmentFromNodeChildren: fragmentFromNodeChildren, createIterator: createIterator, DomPosition: DomPosition }; api.DOMException = DOMException; });rangy.createModule("DomRange", function(api, module) { api.requireModules( ["DomUtil"] ); var dom = api.dom; var DomPosition = dom.DomPosition; var DOMException = api.DOMException; /*----------------------------------------------------------------------------------------------------------------*/ // Utility functions function isNonTextPartiallySelected(node, range) { return (node.nodeType != 3) && (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true)); } function getRangeDocument(range) { return dom.getDocument(range.startContainer); } function dispatchEvent(range, type, args) { var listeners = range._listeners[type]; if (listeners) { for (var i = 0, len = listeners.length; i < len; ++i) { listeners[i].call(range, {target: range, args: args}); } } } function getBoundaryBeforeNode(node) { return new DomPosition(node.parentNode, dom.getNodeIndex(node)); } function getBoundaryAfterNode(node) { return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1); } function insertNodeAtPosition(node, n, o) { var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; if (dom.isCharacterDataNode(n)) { if (o == n.length) { dom.insertAfter(node, n); } else { n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o)); } } else if (o >= n.childNodes.length) { n.appendChild(node); } else { n.insertBefore(node, n.childNodes[o]); } return firstNodeInserted; } function cloneSubtree(iterator) { var partiallySelected; for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { partiallySelected = iterator.isPartiallySelectedSubtree(); node = node.cloneNode(!partiallySelected); if (partiallySelected) { subIterator = iterator.getSubtreeIterator(); node.appendChild(cloneSubtree(subIterator)); subIterator.detach(true); } if (node.nodeType == 10) { // DocumentType throw new DOMException("HIERARCHY_REQUEST_ERR"); } frag.appendChild(node); } return frag; } function iterateSubtree(rangeIterator, func, iteratorState) { var it, n; iteratorState = iteratorState || { stop: false }; for (var node, subRangeIterator; node = rangeIterator.next(); ) { //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node)); if (rangeIterator.isPartiallySelectedSubtree()) { // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the // node selected by the Range. if (func(node) === false) { iteratorState.stop = true; return; } else { subRangeIterator = rangeIterator.getSubtreeIterator(); iterateSubtree(subRangeIterator, func, iteratorState); subRangeIterator.detach(true); if (iteratorState.stop) { return; } } } else { // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its // descendant it = dom.createIterator(node); while ( (n = it.next()) ) { if (func(n) === false) { iteratorState.stop = true; return; } } } } } function deleteSubtree(iterator) { var subIterator; while (iterator.next()) { if (iterator.isPartiallySelectedSubtree()) { subIterator = iterator.getSubtreeIterator(); deleteSubtree(subIterator); subIterator.detach(true); } else { iterator.remove(); } } } function extractSubtree(iterator) { for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { if (iterator.isPartiallySelectedSubtree()) { node = node.cloneNode(false); subIterator = iterator.getSubtreeIterator(); node.appendChild(extractSubtree(subIterator)); subIterator.detach(true); } else { iterator.remove(); } if (node.nodeType == 10) { // DocumentType throw new DOMException("HIERARCHY_REQUEST_ERR"); } frag.appendChild(node); } return frag; } function getNodesInRange(range, nodeTypes, filter) { //log.info("getNodesInRange, " + nodeTypes.join(",")); var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; var filterExists = !!filter; if (filterNodeTypes) { regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); } var nodes = []; iterateSubtree(new RangeIterator(range, false), function(node) { if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { nodes.push(node); } }); return nodes; } function inspect(range) { var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; } /*----------------------------------------------------------------------------------------------------------------*/ // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) /** * @constructor */ function RangeIterator(range, clonePartiallySelectedTextNodes) { this.range = range; this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; if (!range.collapsed) { this.sc = range.startContainer; this.so = range.startOffset; this.ec = range.endContainer; this.eo = range.endOffset; var root = range.commonAncestorContainer; if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) { this.isSingleCharacterDataNode = true; this._first = this._last = this._next = this.sc; } else { this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true); this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true); } } } RangeIterator.prototype = { _current: null, _next: null, _first: null, _last: null, isSingleCharacterDataNode: false, reset: function() { this._current = null; this._next = this._first; }, hasNext: function() { return !!this._next; }, next: function() { // Move to next node var current = this._current = this._next; if (current) { this._next = (current !== this._last) ? current.nextSibling : null; // Check for partially selected text nodes if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { if (current === this.ec) { (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); } if (this._current === this.sc) { (current = current.cloneNode(true)).deleteData(0, this.so); } } } return current; }, remove: function() { var current = this._current, start, end; if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { start = (current === this.sc) ? this.so : 0; end = (current === this.ec) ? this.eo : current.length; if (start != end) { current.deleteData(start, end - start); } } else { if (current.parentNode) { current.parentNode.removeChild(current); } else { } } }, // Checks if the current node is partially selected isPartiallySelectedSubtree: function() { var current = this._current; return isNonTextPartiallySelected(current, this.range); }, getSubtreeIterator: function() { var subRange; if (this.isSingleCharacterDataNode) { subRange = this.range.cloneRange(); subRange.collapse(); } else { subRange = new Range(getRangeDocument(this.range)); var current = this._current; var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current); if (dom.isAncestorOf(current, this.sc, true)) { startContainer = this.sc; startOffset = this.so; } if (dom.isAncestorOf(current, this.ec, true)) { endContainer = this.ec; endOffset = this.eo; } updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); } return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); }, detach: function(detachRange) { if (detachRange) { this.range.detach(); } this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; } }; /*----------------------------------------------------------------------------------------------------------------*/ // Exceptions /** * @constructor */ function RangeException(codeName) { this.code = this[codeName]; this.codeName = codeName; this.message = "RangeException: " + this.codeName; } RangeException.prototype = { BAD_BOUNDARYPOINTS_ERR: 1, INVALID_NODE_TYPE_ERR: 2 }; RangeException.prototype.toString = function() { return this.message; }; /*----------------------------------------------------------------------------------------------------------------*/ /** * Currently iterates through all nodes in the range on creation until I think of a decent way to do it * TODO: Look into making this a proper iterator, not requiring preloading everything first * @constructor */ function RangeNodeIterator(range, nodeTypes, filter) { this.nodes = getNodesInRange(range, nodeTypes, filter); this._next = this.nodes[0]; this._position = 0; } RangeNodeIterator.prototype = { _current: null, hasNext: function() { return !!this._next; }, next: function() { this._current = this._next; this._next = this.nodes[ ++this._position ]; return this._current; }, detach: function() { this._current = this._next = this.nodes = null; } }; var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; var rootContainerNodeTypes = [2, 9, 11]; var readonlyNodeTypes = [5, 6, 10, 12]; var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; function createAncestorFinder(nodeTypes) { return function(node, selfIsAncestor) { var t, n = selfIsAncestor ? node : node.parentNode; while (n) { t = n.nodeType; if (dom.arrayContains(nodeTypes, t)) { return n; } n = n.parentNode; } return null; }; } var getRootContainer = dom.getRootContainer; var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { if (getDocTypeNotationEntityAncestor(node, allowSelf)) { throw new RangeException("INVALID_NODE_TYPE_ERR"); } } function assertNotDetached(range) { if (!range.startContainer) { throw new DOMException("INVALID_STATE_ERR"); } } function assertValidNodeType(node, invalidTypes) { if (!dom.arrayContains(invalidTypes, node.nodeType)) { throw new RangeException("INVALID_NODE_TYPE_ERR"); } } function assertValidOffset(node, offset) { if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) { throw new DOMException("INDEX_SIZE_ERR"); } } function assertSameDocumentOrFragment(node1, node2) { if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { throw new DOMException("WRONG_DOCUMENT_ERR"); } } function assertNodeNotReadOnly(node) { if (getReadonlyAncestor(node, true)) { throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); } } function assertNode(node, codeName) { if (!node) { throw new DOMException(codeName); } } function isOrphan(node) { return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); } function isValidOffset(node, offset) { return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length); } function isRangeValid(range) { return (!!range.startContainer && !!range.endContainer && !isOrphan(range.startContainer) && !isOrphan(range.endContainer) && isValidOffset(range.startContainer, range.startOffset) && isValidOffset(range.endContainer, range.endOffset)); } function assertRangeValid(range) { assertNotDetached(range); if (!isRangeValid(range)) { throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); } } /*----------------------------------------------------------------------------------------------------------------*/ // Test the browser's innerHTML support to decide how to implement createContextualFragment var styleEl = document.createElement("style"); var htmlParsingConforms = false; try { styleEl.innerHTML = "x"; htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node } catch (e) { // IE 6 and 7 throw } api.features.htmlParsingConforms = htmlParsingConforms; var createContextualFragment = htmlParsingConforms ? // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See // discussion and base code for this implementation at issue 67. // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface // Thanks to Aleks Williams. function(fragmentStr) { // "Let node the context object's start's node." var node = this.startContainer; var doc = dom.getDocument(node); // "If the context object's start's node is null, raise an INVALID_STATE_ERR // exception and abort these steps." if (!node) { throw new DOMException("INVALID_STATE_ERR"); } // "Let element be as follows, depending on node's interface:" // Document, Document Fragment: null var el = null; // "Element: node" if (node.nodeType == 1) { el = node; // "Text, Comment: node's parentElement" } else if (dom.isCharacterDataNode(node)) { el = dom.parentElement(node); } // "If either element is null or element's ownerDocument is an HTML document // and element's local name is "html" and element's namespace is the HTML // namespace" if (el === null || ( el.nodeName == "HTML" && dom.isHtmlNamespace(dom.getDocument(el).documentElement) && dom.isHtmlNamespace(el) )) { // "let element be a new Element with "body" as its local name and the HTML // namespace as its namespace."" el = doc.createElement("body"); } else { el = el.cloneNode(false); } // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." // "In either case, the algorithm must be invoked with fragment as the input // and element as the context element." el.innerHTML = fragmentStr; // "If this raises an exception, then abort these steps. Otherwise, let new // children be the nodes returned." // "Let fragment be a new DocumentFragment." // "Append all new children to fragment." // "Return fragment." return dom.fragmentFromNodeChildren(el); } : // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that // previous versions of Rangy used (with the exception of using a body element rather than a div) function(fragmentStr) { assertNotDetached(this); var doc = getRangeDocument(this); var el = doc.createElement("body"); el.innerHTML = fragmentStr; return dom.fragmentFromNodeChildren(el); }; /*----------------------------------------------------------------------------------------------------------------*/ var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", "commonAncestorContainer"]; var s2s = 0, s2e = 1, e2e = 2, e2s = 3; var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; function RangePrototype() {} RangePrototype.prototype = { attachListener: function(type, listener) { this._listeners[type].push(listener); }, compareBoundaryPoints: function(how, range) { assertRangeValid(this); assertSameDocumentOrFragment(this.startContainer, range.startContainer); var nodeA, offsetA, nodeB, offsetB; var prefixA = (how == e2s || how == s2s) ? "start" : "end"; var prefixB = (how == s2e || how == s2s) ? "start" : "end"; nodeA = this[prefixA + "Container"]; offsetA = this[prefixA + "Offset"]; nodeB = range[prefixB + "Container"]; offsetB = range[prefixB + "Offset"]; return dom.comparePoints(nodeA, offsetA, nodeB, offsetB); }, insertNode: function(node) { assertRangeValid(this); assertValidNodeType(node, insertableNodeTypes); assertNodeNotReadOnly(this.startContainer); if (dom.isAncestorOf(node, this.startContainer, true)) { throw new DOMException("HIERARCHY_REQUEST_ERR"); } // No check for whether the container of the start of the Range is of a type that does not allow // children of the type of node: the browser's DOM implementation should do this for us when we attempt // to add the node var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); this.setStartBefore(firstNodeInserted); }, cloneContents: function() { assertRangeValid(this); var clone, frag; if (this.collapsed) { return getRangeDocument(this).createDocumentFragment(); } else { if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) { clone = this.startContainer.cloneNode(true); clone.data = clone.data.slice(this.startOffset, this.endOffset); frag = getRangeDocument(this).createDocumentFragment(); frag.appendChild(clone); return frag; } else { var iterator = new RangeIterator(this, true); clone = cloneSubtree(iterator); iterator.detach(); } return clone; } }, canSurroundContents: function() { assertRangeValid(this); assertNodeNotReadOnly(this.startContainer); assertNodeNotReadOnly(this.endContainer); // Check if the contents can be surrounded. Specifically, this means whether the range partially selects // no non-text nodes. var iterator = new RangeIterator(this, true); var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this))); iterator.detach(); return !boundariesInvalid; }, surroundContents: function(node) { assertValidNodeType(node, surroundNodeTypes); if (!this.canSurroundContents()) { throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); } // Extract the contents var content = this.extractContents(); // Clear the children of the node if (node.hasChildNodes()) { while (node.lastChild) { node.removeChild(node.lastChild); } } // Insert the new node and add the extracted contents insertNodeAtPosition(node, this.startContainer, this.startOffset); node.appendChild(content); this.selectNode(node); }, cloneRange: function() { assertRangeValid(this); var range = new Range(getRangeDocument(this)); var i = rangeProperties.length, prop; while (i--) { prop = rangeProperties[i]; range[prop] = this[prop]; } return range; }, toString: function() { assertRangeValid(this); var sc = this.startContainer; if (sc === this.endContainer && dom.isCharacterDataNode(sc)) { return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; } else { var textBits = [], iterator = new RangeIterator(this, true); iterateSubtree(iterator, function(node) { // Accept only text or CDATA nodes, not comments if (node.nodeType == 3 || node.nodeType == 4) { textBits.push(node.data); } }); iterator.detach(); return textBits.join(""); } }, // The methods below are all non-standard. The following batch were introduced by Mozilla but have since // been removed from Mozilla. compareNode: function(node) { assertRangeValid(this); var parent = node.parentNode; var nodeIndex = dom.getNodeIndex(node); if (!parent) { throw new DOMException("NOT_FOUND_ERR"); } var startComparison = this.comparePoint(parent, nodeIndex), endComparison = this.comparePoint(parent, nodeIndex + 1); if (startComparison < 0) { // Node starts before return (endComparison > 0) ? n_b_a : n_b; } else { return (endComparison > 0) ? n_a : n_i; } }, comparePoint: function(node, offset) { assertRangeValid(this); assertNode(node, "HIERARCHY_REQUEST_ERR"); assertSameDocumentOrFragment(node, this.startContainer); if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { return -1; } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { return 1; } return 0; }, createContextualFragment: createContextualFragment, toHtml: function() { assertRangeValid(this); var container = getRangeDocument(this).createElement("div"); container.appendChild(this.cloneContents()); return container.innerHTML; }, // touchingIsIntersecting determines whether this method considers a node that borders a range intersects // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) intersectsNode: function(node, touchingIsIntersecting) { assertRangeValid(this); assertNode(node, "NOT_FOUND_ERR"); if (dom.getDocument(node) !== getRangeDocument(this)) { return false; } var parent = node.parentNode, offset = dom.getNodeIndex(node); assertNode(parent, "NOT_FOUND_ERR"); var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset), endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset); return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; }, isPointInRange: function(node, offset) { assertRangeValid(this); assertNode(node, "HIERARCHY_REQUEST_ERR"); assertSameDocumentOrFragment(node, this.startContainer); return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); }, // The methods below are non-standard and invented by me. // Sharing a boundary start-to-end or end-to-start does not count as intersection. intersectsRange: function(range, touchingIsIntersecting) { assertRangeValid(this); if (getRangeDocument(range) != getRangeDocument(this)) { throw new DOMException("WRONG_DOCUMENT_ERR"); } var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset), endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset); return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; }, intersection: function(range) { if (this.intersectsRange(range)) { var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); var intersectionRange = this.cloneRange(); if (startComparison == -1) { intersectionRange.setStart(range.startContainer, range.startOffset); } if (endComparison == 1) { intersectionRange.setEnd(range.endContainer, range.endOffset); } return intersectionRange; } return null; }, union: function(range) { if (this.intersectsRange(range, true)) { var unionRange = this.cloneRange(); if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { unionRange.setStart(range.startContainer, range.startOffset); } if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { unionRange.setEnd(range.endContainer, range.endOffset); } return unionRange; } else { throw new RangeException("Ranges do not intersect"); } }, containsNode: function(node, allowPartial) { if (allowPartial) { return this.intersectsNode(node, false); } else { return this.compareNode(node) == n_i; } }, containsNodeContents: function(node) { return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0; }, containsRange: function(range) { return this.intersection(range).equals(range); }, containsNodeText: function(node) { var nodeRange = this.cloneRange(); nodeRange.selectNode(node); var textNodes = nodeRange.getNodes([3]); if (textNodes.length > 0) { nodeRange.setStart(textNodes[0], 0); var lastTextNode = textNodes.pop(); nodeRange.setEnd(lastTextNode, lastTextNode.length); var contains = this.containsRange(nodeRange); nodeRange.detach(); return contains; } else { return this.containsNodeContents(node); } }, createNodeIterator: function(nodeTypes, filter) { assertRangeValid(this); return new RangeNodeIterator(this, nodeTypes, filter); }, getNodes: function(nodeTypes, filter) { assertRangeValid(this); return getNodesInRange(this, nodeTypes, filter); }, getDocument: function() { return getRangeDocument(this); }, collapseBefore: function(node) { assertNotDetached(this); this.setEndBefore(node); this.collapse(false); }, collapseAfter: function(node) { assertNotDetached(this); this.setStartAfter(node); this.collapse(true); }, getName: function() { return "DomRange"; }, equals: function(range) { return Range.rangesEqual(this, range); }, isValid: function() { return isRangeValid(this); }, inspect: function() { return inspect(this); } }; function copyComparisonConstantsToObject(obj) { obj.START_TO_START = s2s; obj.START_TO_END = s2e; obj.END_TO_END = e2e; obj.END_TO_START = e2s; obj.NODE_BEFORE = n_b; obj.NODE_AFTER = n_a; obj.NODE_BEFORE_AND_AFTER = n_b_a; obj.NODE_INSIDE = n_i; } function copyComparisonConstants(constructor) { copyComparisonConstantsToObject(constructor); copyComparisonConstantsToObject(constructor.prototype); } function createRangeContentRemover(remover, boundaryUpdater) { return function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; var iterator = new RangeIterator(this, true); // Work out where to position the range after content removal var node, boundary; if (sc !== root) { node = dom.getClosestAncestorIn(sc, root, true); boundary = getBoundaryAfterNode(node); sc = boundary.node; so = boundary.offset; } // Check none of the range is read-only iterateSubtree(iterator, assertNodeNotReadOnly); iterator.reset(); // Remove the content var returnValue = remover(iterator); iterator.detach(); // Move to the new position boundaryUpdater(this, sc, so, sc, so); return returnValue; }; } function createPrototypeRange(constructor, boundaryUpdater, detacher) { function createBeforeAfterNodeSetter(isBefore, isStart) { return function(node) { assertNotDetached(this); assertValidNodeType(node, beforeAfterNodeTypes); assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); }; } function setRangeStart(range, node, offset) { var ec = range.endContainer, eo = range.endOffset; if (node !== range.startContainer || offset !== range.startOffset) { // Check the root containers of the range and the new boundary, and also check whether the new boundary // is after the current end. In either case, collapse the range to the new position if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) { ec = node; eo = offset; } boundaryUpdater(range, node, offset, ec, eo); } } function setRangeEnd(range, node, offset) { var sc = range.startContainer, so = range.startOffset; if (node !== range.endContainer || offset !== range.endOffset) { // Check the root containers of the range and the new boundary, and also check whether the new boundary // is after the current end. In either case, collapse the range to the new position if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) { sc = node; so = offset; } boundaryUpdater(range, sc, so, node, offset); } } function setRangeStartAndEnd(range, node, offset) { if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) { boundaryUpdater(range, node, offset, node, offset); } } constructor.prototype = new RangePrototype(); api.util.extend(constructor.prototype, { setStart: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeStart(this, node, offset); }, setEnd: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeEnd(this, node, offset); }, setStartBefore: createBeforeAfterNodeSetter(true, true), setStartAfter: createBeforeAfterNodeSetter(false, true), setEndBefore: createBeforeAfterNodeSetter(true, false), setEndAfter: createBeforeAfterNodeSetter(false, false), collapse: function(isStart) { assertRangeValid(this); if (isStart) { boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); } else { boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); } }, selectNodeContents: function(node) { // This doesn't seem well specified: the spec talks only about selecting the node's contents, which // could be taken to mean only its children. However, browsers implement this the same as selectNode for // text nodes, so I shall do likewise assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); boundaryUpdater(this, node, 0, node, dom.getNodeLength(node)); }, selectNode: function(node) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, false); assertValidNodeType(node, beforeAfterNodeTypes); var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); boundaryUpdater(this, start.node, start.offset, end.node, end.offset); }, extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), canSurroundContents: function() { assertRangeValid(this); assertNodeNotReadOnly(this.startContainer); assertNodeNotReadOnly(this.endContainer); // Check if the contents can be surrounded. Specifically, this means whether the range partially selects // no non-text nodes. var iterator = new RangeIterator(this, true); var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || (iterator._last && isNonTextPartiallySelected(iterator._last, this))); iterator.detach(); return !boundariesInvalid; }, detach: function() { detacher(this); }, splitBoundaries: function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; var startEndSame = (sc === ec); if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { dom.splitDataNode(ec, eo); } if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) { sc = dom.splitDataNode(sc, so); if (startEndSame) { eo -= so; ec = sc; } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) { eo++; } so = 0; } boundaryUpdater(this, sc, so, ec, eo); }, normalizeBoundaries: function() { assertRangeValid(this); var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; var mergeForward = function(node) { var sibling = node.nextSibling; if (sibling && sibling.nodeType == node.nodeType) { ec = node; eo = node.length; node.appendData(sibling.data); sibling.parentNode.removeChild(sibling); } }; var mergeBackward = function(node) { var sibling = node.previousSibling; if (sibling && sibling.nodeType == node.nodeType) { sc = node; var nodeLength = node.length; so = sibling.length; node.insertData(0, sibling.data); sibling.parentNode.removeChild(sibling); if (sc == ec) { eo += so; ec = sc; } else if (ec == node.parentNode) { var nodeIndex = dom.getNodeIndex(node); if (eo == nodeIndex) { ec = node; eo = nodeLength; } else if (eo > nodeIndex) { eo--; } } } }; var normalizeStart = true; if (dom.isCharacterDataNode(ec)) { if (ec.length == eo) { mergeForward(ec); } } else { if (eo > 0) { var endNode = ec.childNodes[eo - 1]; if (endNode && dom.isCharacterDataNode(endNode)) { mergeForward(endNode); } } normalizeStart = !this.collapsed; } if (normalizeStart) { if (dom.isCharacterDataNode(sc)) { if (so == 0) { mergeBackward(sc); } } else { if (so < sc.childNodes.length) { var startNode = sc.childNodes[so]; if (startNode && dom.isCharacterDataNode(startNode)) { mergeBackward(startNode); } } } } else { sc = ec; so = eo; } boundaryUpdater(this, sc, so, ec, eo); }, collapseToPoint: function(node, offset) { assertNotDetached(this); assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); setRangeStartAndEnd(this, node, offset); } }); copyComparisonConstants(constructor); } /*----------------------------------------------------------------------------------------------------------------*/ // Updates commonAncestorContainer and collapsed after boundary change function updateCollapsedAndCommonAncestor(range) { range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); range.commonAncestorContainer = range.collapsed ? range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); } function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset); var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset); range.startContainer = startContainer; range.startOffset = startOffset; range.endContainer = endContainer; range.endOffset = endOffset; updateCollapsedAndCommonAncestor(range); dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved}); } function detach(range) { assertNotDetached(range); range.startContainer = range.startOffset = range.endContainer = range.endOffset = null; range.collapsed = range.commonAncestorContainer = null; dispatchEvent(range, "detach", null); range._listeners = null; } /** * @constructor */ function Range(doc) { this.startContainer = doc; this.startOffset = 0; this.endContainer = doc; this.endOffset = 0; this._listeners = { boundarychange: [], detach: [] }; updateCollapsedAndCommonAncestor(this); } createPrototypeRange(Range, updateBoundaries, detach); api.rangePrototype = RangePrototype.prototype; Range.rangeProperties = rangeProperties; Range.RangeIterator = RangeIterator; Range.copyComparisonConstants = copyComparisonConstants; Range.createPrototypeRange = createPrototypeRange; Range.inspect = inspect; Range.getRangeDocument = getRangeDocument; Range.rangesEqual = function(r1, r2) { return r1.startContainer === r2.startContainer && r1.startOffset === r2.startOffset && r1.endContainer === r2.endContainer && r1.endOffset === r2.endOffset; }; api.DomRange = Range; api.RangeException = RangeException; });rangy.createModule("WrappedRange", function(api, module) { api.requireModules( ["DomUtil", "DomRange"] ); /** * @constructor */ var WrappedRange; var dom = api.dom; var DomPosition = dom.DomPosition; var DomRange = api.DomRange; /*----------------------------------------------------------------------------------------------------------------*/ /* This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() method. For example, in the following (where pipes denote the selection boundaries): var range = document.selection.createRange(); alert(range.parentElement().id); // Should alert "ul" but alerts "b" This method returns the common ancestor node of the following: - the parentElement() of the textRange - the parentElement() of the textRange after calling collapse(true) - the parentElement() of the textRange after calling collapse(false) */ function getTextRangeContainerElement(textRange) { var parentEl = textRange.parentElement(); var range = textRange.duplicate(); range.collapse(true); var startEl = range.parentElement(); range = textRange.duplicate(); range.collapse(false); var endEl = range.parentElement(); var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); } function textRangeIsCollapsed(textRange) { return textRange.compareEndPoints("StartToEnd", textRange) == 0; } // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling // for inputs and images, plus optimizations. function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) { var workingRange = textRange.duplicate(); workingRange.collapse(isStart); var containerElement = workingRange.parentElement(); // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so // check for that // TODO: Find out when. Workaround for wholeRangeContainerElement may break this if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) { containerElement = wholeRangeContainerElement; } // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx if (!containerElement.canHaveHTML) { return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); } var workingNode = dom.getDocument(containerElement).createElement("span"); var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; var previousNode, nextNode, boundaryPosition, boundaryNode; // Move the working range through the container's children, starting at the end and working backwards, until the // working range reaches or goes past the boundary we're interested in do { containerElement.insertBefore(workingNode, workingNode.previousSibling); workingRange.moveToElementText(workingNode); } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && workingNode.previousSibling); // We've now reached or gone past the boundary of the text range we're interested in // so have identified the node we want boundaryNode = workingNode.nextSibling; if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) { // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the // node containing the text range's boundary, so we move the end of the working range to the boundary point // and measure the length of its text to get the boundary's offset within the node. workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); var offset; if (/[\r\n]/.test(boundaryNode.data)) { /* For the particular case of a boundary within a text node containing line breaks (within a
 element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as \r in the text node's data/nodeValue properties
                - Each line break is represented as \r\n in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
                var tempRange = workingRange.duplicate();
                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;

                offset = tempRange.moveStart("character", rangeLength);
                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
                    offset++;
                    tempRange.moveStart("character", 1);
                }
            } else {
                offset = workingRange.text.length;
            }
            boundaryPosition = new DomPosition(boundaryNode, offset);
        } else {


            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
            // a position within that, and likewise for a start boundary preceding a character data node
            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



            if (nextNode && dom.isCharacterDataNode(nextNode)) {
                boundaryPosition = new DomPosition(nextNode, 0);
            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
                boundaryPosition = new DomPosition(previousNode, previousNode.length);
            } else {
                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
            }
        }

        // Clean up
        workingNode.parentNode.removeChild(workingNode);

        return boundaryPosition;
    }

    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    // (http://code.google.com/p/ierange/)
    function createBoundaryTextRange(boundaryPosition, isStart) {
        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
        var doc = dom.getDocument(boundaryPosition.node);
        var workingNode, childNodes, workingRange = doc.body.createTextRange();
        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);

        if (nodeIsDataNode) {
            boundaryNode = boundaryPosition.node;
            boundaryParent = boundaryNode.parentNode;
        } else {
            childNodes = boundaryPosition.node.childNodes;
            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
            boundaryParent = boundaryPosition.node;
        }

        // Position the range immediately before the node containing the boundary
        workingNode = doc.createElement("span");

        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
        // element rather than immediately before or after it, which is what we want
        workingNode.innerHTML = "&#feff;";

        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
        if (boundaryNode) {
            boundaryParent.insertBefore(workingNode, boundaryNode);
        } else {
            boundaryParent.appendChild(workingNode);
        }

        workingRange.moveToElementText(workingNode);
        workingRange.collapse(!isStart);

        // Clean up
        boundaryParent.removeChild(workingNode);

        // Move the working range to the text offset, if required
        if (nodeIsDataNode) {
            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
        }

        return workingRange;
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
        // This is a wrapper around the browser's native DOM Range. It has two aims:
        // - Provide workarounds for specific browser bugs
        // - provide convenient extensions, which are inherited from Rangy's DomRange

        (function() {
            var rangeProto;
            var rangeProperties = DomRange.rangeProperties;
            var canSetRangeStartAfterEnd;

            function updateRangeProperties(range) {
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = range.nativeRange[prop];
                }
            }

            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

                // Always set both boundaries for the benefit of IE9 (see issue 35)
                if (startMoved || endMoved) {
                    range.setEnd(endContainer, endOffset);
                    range.setStart(startContainer, startOffset);
                }
            }

            function detach(range) {
                range.nativeRange.detach();
                range.detached = true;
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = null;
                }
            }

            var createBeforeAfterNodeSetter;

            WrappedRange = function(range) {
                if (!range) {
                    throw new Error("Range must be specified");
                }
                this.nativeRange = range;
                updateRangeProperties(this);
            };

            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);

            rangeProto = WrappedRange.prototype;

            rangeProto.selectNode = function(node) {
                this.nativeRange.selectNode(node);
                updateRangeProperties(this);
            };

            rangeProto.deleteContents = function() {
                this.nativeRange.deleteContents();
                updateRangeProperties(this);
            };

            rangeProto.extractContents = function() {
                var frag = this.nativeRange.extractContents();
                updateRangeProperties(this);
                return frag;
            };

            rangeProto.cloneContents = function() {
                return this.nativeRange.cloneContents();
            };

            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
            // insertNode, which works but is almost certainly slower than the native implementation.
/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

            rangeProto.surroundContents = function(node) {
                this.nativeRange.surroundContents(node);
                updateRangeProperties(this);
            };

            rangeProto.collapse = function(isStart) {
                this.nativeRange.collapse(isStart);
                updateRangeProperties(this);
            };

            rangeProto.cloneRange = function() {
                return new WrappedRange(this.nativeRange.cloneRange());
            };

            rangeProto.refresh = function() {
                updateRangeProperties(this);
            };

            rangeProto.toString = function() {
                return this.nativeRange.toString();
            };

            // Create test range and node for feature detection

            var testTextNode = document.createTextNode("test");
            dom.getBody(document).appendChild(testTextNode);
            var range = document.createRange();

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
            // correct for it

            range.setStart(testTextNode, 0);
            range.setEnd(testTextNode, 0);

            try {
                range.setStart(testTextNode, 1);
                canSetRangeStartAfterEnd = true;

                rangeProto.setStart = function(node, offset) {
                    this.nativeRange.setStart(node, offset);
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    this.nativeRange.setEnd(node, offset);
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name) {
                    return function(node) {
                        this.nativeRange[name](node);
                        updateRangeProperties(this);
                    };
                };

            } catch(ex) {


                canSetRangeStartAfterEnd = false;

                rangeProto.setStart = function(node, offset) {
                    try {
                        this.nativeRange.setStart(node, offset);
                    } catch (ex) {
                        this.nativeRange.setEnd(node, offset);
                        this.nativeRange.setStart(node, offset);
                    }
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    try {
                        this.nativeRange.setEnd(node, offset);
                    } catch (ex) {
                        this.nativeRange.setStart(node, offset);
                        this.nativeRange.setEnd(node, offset);
                    }
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name, oppositeName) {
                    return function(node) {
                        try {
                            this.nativeRange[name](node);
                        } catch (ex) {
                            this.nativeRange[oppositeName](node);
                            this.nativeRange[name](node);
                        }
                        updateRangeProperties(this);
                    };
                };
            }

            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
            // the 0th character of the text node
            range.selectNodeContents(testTextNode);
            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
                rangeProto.selectNodeContents = function(node) {
                    this.nativeRange.selectNodeContents(node);
                    updateRangeProperties(this);
                };
            } else {
                rangeProto.selectNodeContents = function(node) {
                    this.setStart(node, 0);
                    this.setEnd(node, DomRange.getEndOffset(node));
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

            range.selectNodeContents(testTextNode);
            range.setEnd(testTextNode, 3);

            var range2 = document.createRange();
            range2.selectNodeContents(testTextNode);
            range2.setEnd(testTextNode, 4);
            range2.setStart(testTextNode, 2);

            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
                // This is the wrong way round, so correct for it


                rangeProto.compareBoundaryPoints = function(type, range) {
                    range = range.nativeRange || range;
                    if (type == range.START_TO_END) {
                        type = range.END_TO_START;
                    } else if (type == range.END_TO_START) {
                        type = range.START_TO_END;
                    }
                    return this.nativeRange.compareBoundaryPoints(type, range);
                };
            } else {
                rangeProto.compareBoundaryPoints = function(type, range) {
                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for existence of createContextualFragment and delegate to it if it exists
            if (api.util.isHostMethod(range, "createContextualFragment")) {
                rangeProto.createContextualFragment = function(fragmentStr) {
                    return this.nativeRange.createContextualFragment(fragmentStr);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Clean up
            dom.getBody(document).removeChild(testTextNode);
            range.detach();
            range2.detach();
        })();

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.createRange();
        };
    } else if (api.features.implementsTextRange) {
        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
        // prototype

        WrappedRange = function(textRange) {
            this.textRange = textRange;
            this.refresh();
        };

        WrappedRange.prototype = new DomRange(document);

        WrappedRange.prototype.refresh = function() {
            var start, end;

            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
            var rangeContainerElement = getTextRangeContainerElement(this.textRange);

            if (textRangeIsCollapsed(this.textRange)) {
                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
            } else {

                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
            }

            this.setStart(start.node, start.offset);
            this.setEnd(end.node, end.offset);
        };

        DomRange.copyComparisonConstants(WrappedRange);

        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
        var globalObj = (function() { return this; })();
        if (typeof globalObj.Range == "undefined") {
            globalObj.Range = WrappedRange;
        }

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.body.createTextRange();
        };
    }

    if (api.features.implementsTextRange) {
        WrappedRange.rangeToTextRange = function(range) {
            if (range.collapsed) {
                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);



                return tr;

                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
            } else {
                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
                textRange.setEndPoint("StartToStart", startRange);
                textRange.setEndPoint("EndToEnd", endRange);
                return textRange;
            }
        };
    }

    WrappedRange.prototype.getName = function() {
        return "WrappedRange";
    };

    api.WrappedRange = WrappedRange;

    api.createRange = function(doc) {
        doc = doc || document;
        return new WrappedRange(api.createNativeRange(doc));
    };

    api.createRangyRange = function(doc) {
        doc = doc || document;
        return new DomRange(doc);
    };

    api.createIframeRange = function(iframeEl) {
        return api.createRange(dom.getIframeDocument(iframeEl));
    };

    api.createIframeRangyRange = function(iframeEl) {
        return api.createRangyRange(dom.getIframeDocument(iframeEl));
    };

    api.addCreateMissingNativeApiListener(function(win) {
        var doc = win.document;
        if (typeof doc.createRange == "undefined") {
            doc.createRange = function() {
                return api.createRange(this);
            };
        }
        doc = win = null;
    });
});rangy.createModule("WrappedSelection", function(api, module) {
    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
    // spec (http://html5.org/specs/dom-range.html)

    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );

    api.config.checkSelectionRanges = true;

    var BOOLEAN = "boolean",
        windowPropertyName = "_rangySelection",
        dom = api.dom,
        util = api.util,
        DomRange = api.DomRange,
        WrappedRange = api.WrappedRange,
        DOMException = api.DOMException,
        DomPosition = dom.DomPosition,
        getSelection,
        selectionIsCollapsed,
        CONTROL = "Control";



    function getWinSelection(winParam) {
        return (winParam || window).getSelection();
    }

    function getDocSelection(winParam) {
        return (winParam || window).document.selection;
    }

    // Test for the Range/TextRange and Selection features required
    // Test for ability to retrieve selection
    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
        implementsDocSelection = api.util.isHostObject(document, "selection");

    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

    if (useDocumentSelection) {
        getSelection = getDocSelection;
        api.isSelectionValid = function(winParam) {
            var doc = (winParam || window).document, nativeSel = doc.selection;

            // Check whether the selection TextRange is actually contained within the correct document
            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
        };
    } else if (implementsWinGetSelection) {
        getSelection = getWinSelection;
        api.isSelectionValid = function() {
            return true;
        };
    } else {
        module.fail("Neither document.selection or window.getSelection() detected.");
    }

    api.getNativeSelection = getSelection;

    var testSelection = getSelection();
    var testRange = api.createNativeRange(document);
    var body = dom.getBody(document);

    // Obtaining a range from a selection
    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;

    // Test for existence of native selection extend() method
    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
    api.features.selectionHasExtend = selectionHasExtend;

    // Test if rangeCount exists
    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
    api.features.selectionHasRangeCount = selectionHasRangeCount;

    var selectionSupportsMultipleRanges = false;
    var collapsedNonEditableSelectionsSupported = true;

    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

        (function() {
            var iframe = document.createElement("iframe");
            iframe.frameBorder = 0;
            iframe.style.position = "absolute";
            iframe.style.left = "-10000px";
            body.appendChild(iframe);

            var iframeDoc = dom.getIframeDocument(iframe);
            iframeDoc.open();
            iframeDoc.write("12");
            iframeDoc.close();

            var sel = dom.getIframeWindow(iframe).getSelection();
            var docEl = iframeDoc.documentElement;
            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;

            // Test whether the native selection will allow a collapsed selection within a non-editable element
            var r1 = iframeDoc.createRange();
            r1.setStart(textNode, 1);
            r1.collapse(true);
            sel.addRange(r1);
            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
            sel.removeAllRanges();

            // Test whether the native selection is capable of supporting multiple ranges
            var r2 = r1.cloneRange();
            r1.setStart(textNode, 0);
            r2.setEnd(textNode, 2);
            sel.addRange(r1);
            sel.addRange(r2);

            selectionSupportsMultipleRanges = (sel.rangeCount == 2);

            // Clean up
            r1.detach();
            r2.detach();

            body.removeChild(iframe);
        })();
    }

    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;

    // ControlRanges
    var implementsControlRange = false, testControlRange;

    if (body && util.isHostMethod(body, "createControlRange")) {
        testControlRange = body.createControlRange();
        if (util.areHostProperties(testControlRange, ["item", "add"])) {
            implementsControlRange = true;
        }
    }
    api.features.implementsControlRange = implementsControlRange;

    // Selection collapsedness
    if (selectionHasAnchorAndFocus) {
        selectionIsCollapsed = function(sel) {
            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
        };
    } else {
        selectionIsCollapsed = function(sel) {
            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
        };
    }

    function updateAnchorAndFocusFromRange(sel, range, backwards) {
        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
        sel.anchorNode = range[anchorPrefix + "Container"];
        sel.anchorOffset = range[anchorPrefix + "Offset"];
        sel.focusNode = range[focusPrefix + "Container"];
        sel.focusOffset = range[focusPrefix + "Offset"];
    }

    function updateAnchorAndFocusFromNativeSelection(sel) {
        var nativeSel = sel.nativeSelection;
        sel.anchorNode = nativeSel.anchorNode;
        sel.anchorOffset = nativeSel.anchorOffset;
        sel.focusNode = nativeSel.focusNode;
        sel.focusOffset = nativeSel.focusOffset;
    }

    function updateEmptySelection(sel) {
        sel.anchorNode = sel.focusNode = null;
        sel.anchorOffset = sel.focusOffset = 0;
        sel.rangeCount = 0;
        sel.isCollapsed = true;
        sel._ranges.length = 0;
    }

    function getNativeRange(range) {
        var nativeRange;
        if (range instanceof DomRange) {
            nativeRange = range._selectionNativeRange;
            if (!nativeRange) {
                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
                nativeRange.setEnd(range.endContainer, range.endOffset);
                nativeRange.setStart(range.startContainer, range.startOffset);
                range._selectionNativeRange = nativeRange;
                range.attachListener("detach", function() {

                    this._selectionNativeRange = null;
                });
            }
        } else if (range instanceof WrappedRange) {
            nativeRange = range.nativeRange;
        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
            nativeRange = range;
        }
        return nativeRange;
    }

    function rangeContainsSingleElement(rangeNodes) {
        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
            return false;
        }
        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
                return false;
            }
        }
        return true;
    }

    function getSingleElementFromRange(range) {
        var nodes = range.getNodes();
        if (!rangeContainsSingleElement(nodes)) {
            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
        }
        return nodes[0];
    }

    function isTextRange(range) {
        return !!range && typeof range.text != "undefined";
    }

    function updateFromTextRange(sel, range) {
        // Create a Range from the selected TextRange
        var wrappedRange = new WrappedRange(range);
        sel._ranges = [wrappedRange];

        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
        sel.rangeCount = 1;
        sel.isCollapsed = wrappedRange.collapsed;
    }

    function updateControlSelection(sel) {
        // Update the wrapped selection based on what's now in the native selection
        sel._ranges.length = 0;
        if (sel.docSelection.type == "None") {
            updateEmptySelection(sel);
        } else {
            var controlRange = sel.docSelection.createRange();
            if (isTextRange(controlRange)) {
                // This case (where the selection type is "Control" and calling createRange() on the selection returns
                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
                // ControlRange have been removed from the ControlRange and removed from the document.
                updateFromTextRange(sel, controlRange);
            } else {
                sel.rangeCount = controlRange.length;
                var range, doc = dom.getDocument(controlRange.item(0));
                for (var i = 0; i < sel.rangeCount; ++i) {
                    range = api.createRange(doc);
                    range.selectNode(controlRange.item(i));
                    sel._ranges.push(range);
                }
                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
            }
        }
    }

    function addRangeToControlSelection(sel, range) {
        var controlRange = sel.docSelection.createRange();
        var rangeElement = getSingleElementFromRange(range);

        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
        // contained by the supplied range
        var doc = dom.getDocument(controlRange.item(0));
        var newControlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, len = controlRange.length; i < len; ++i) {
            newControlRange.add(controlRange.item(i));
        }
        try {
            newControlRange.add(rangeElement);
        } catch (ex) {
            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
        }
        newControlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    var getSelectionRangeAt;

    if (util.isHostMethod(testSelection,  "getRangeAt")) {
        getSelectionRangeAt = function(sel, index) {
            try {
                return sel.getRangeAt(index);
            } catch(ex) {
                return null;
            }
        };
    } else if (selectionHasAnchorAndFocus) {
        getSelectionRangeAt = function(sel) {
            var doc = dom.getDocument(sel.anchorNode);
            var range = api.createRange(doc);
            range.setStart(sel.anchorNode, sel.anchorOffset);
            range.setEnd(sel.focusNode, sel.focusOffset);

            // Handle the case when the selection was selected backwards (from the end to the start in the
            // document)
            if (range.collapsed !== this.isCollapsed) {
                range.setStart(sel.focusNode, sel.focusOffset);
                range.setEnd(sel.anchorNode, sel.anchorOffset);
            }

            return range;
        };
    }

    /**
     * @constructor
     */
    function WrappedSelection(selection, docSelection, win) {
        this.nativeSelection = selection;
        this.docSelection = docSelection;
        this._ranges = [];
        this.win = win;
        this.refresh();
    }

    api.getSelection = function(win) {
        win = win || window;
        var sel = win[windowPropertyName];
        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
        if (sel) {
            sel.nativeSelection = nativeSel;
            sel.docSelection = docSel;
            sel.refresh(win);
        } else {
            sel = new WrappedSelection(nativeSel, docSel, win);
            win[windowPropertyName] = sel;
        }
        return sel;
    };

    api.getIframeSelection = function(iframeEl) {
        return api.getSelection(dom.getIframeWindow(iframeEl));
    };

    var selProto = WrappedSelection.prototype;

    function createControlSelection(sel, ranges) {
        // Ensure that the selection becomes of type "Control"
        var doc = dom.getDocument(ranges[0].startContainer);
        var controlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, el; i < rangeCount; ++i) {
            el = getSingleElementFromRange(ranges[i]);
            try {
                controlRange.add(el);
            } catch (ex) {
                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
            }
        }
        controlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    // Selecting a range
    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
        selProto.removeAllRanges = function() {
            this.nativeSelection.removeAllRanges();
            updateEmptySelection(this);
        };

        var addRangeBackwards = function(sel, range) {
            var doc = DomRange.getRangeDocument(range);
            var endRange = api.createRange(doc);
            endRange.collapseToPoint(range.endContainer, range.endOffset);
            sel.nativeSelection.addRange(getNativeRange(endRange));
            sel.nativeSelection.extend(range.startContainer, range.startOffset);
            sel.refresh();
        };

        if (selectionHasRangeCount) {
            selProto.addRange = function(range, backwards) {
                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
                    addRangeToControlSelection(this, range);
                } else {
                    if (backwards && selectionHasExtend) {
                        addRangeBackwards(this, range);
                    } else {
                        var previousRangeCount;
                        if (selectionSupportsMultipleRanges) {
                            previousRangeCount = this.rangeCount;
                        } else {
                            this.removeAllRanges();
                            previousRangeCount = 0;
                        }
                        this.nativeSelection.addRange(getNativeRange(range));

                        // Check whether adding the range was successful
                        this.rangeCount = this.nativeSelection.rangeCount;

                        if (this.rangeCount == previousRangeCount + 1) {
                            // The range was added successfully

                            // Check whether the range that we added to the selection is reflected in the last range extracted from
                            // the selection
                            if (api.config.checkSelectionRanges) {
                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
                                    range = new WrappedRange(nativeRange);
                                }
                            }
                            this._ranges[this.rangeCount - 1] = range;
                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
                            this.isCollapsed = selectionIsCollapsed(this);
                        } else {
                            // The range was not added successfully. The simplest thing is to refresh
                            this.refresh();
                        }
                    }
                }
            };
        } else {
            selProto.addRange = function(range, backwards) {
                if (backwards && selectionHasExtend) {
                    addRangeBackwards(this, range);
                } else {
                    this.nativeSelection.addRange(getNativeRange(range));
                    this.refresh();
                }
            };
        }

        selProto.setRanges = function(ranges) {
            if (implementsControlRange && ranges.length > 1) {
                createControlSelection(this, ranges);
            } else {
                this.removeAllRanges();
                for (var i = 0, len = ranges.length; i < len; ++i) {
                    this.addRange(ranges[i]);
                }
            }
        };
    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
               implementsControlRange && useDocumentSelection) {

        selProto.removeAllRanges = function() {
            // Added try/catch as fix for issue #21
            try {
                this.docSelection.empty();

                // Check for empty() not working (issue #24)
                if (this.docSelection.type != "None") {
                    // Work around failure to empty a control selection by instead selecting a TextRange and then
                    // calling empty()
                    var doc;
                    if (this.anchorNode) {
                        doc = dom.getDocument(this.anchorNode);
                    } else if (this.docSelection.type == CONTROL) {
                        var controlRange = this.docSelection.createRange();
                        if (controlRange.length) {
                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
                        }
                    }
                    if (doc) {
                        var textRange = doc.body.createTextRange();
                        textRange.select();
                        this.docSelection.empty();
                    }
                }
            } catch(ex) {}
            updateEmptySelection(this);
        };

        selProto.addRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                addRangeToControlSelection(this, range);
            } else {
                WrappedRange.rangeToTextRange(range).select();
                this._ranges[0] = range;
                this.rangeCount = 1;
                this.isCollapsed = this._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(this, range, false);
            }
        };

        selProto.setRanges = function(ranges) {
            this.removeAllRanges();
            var rangeCount = ranges.length;
            if (rangeCount > 1) {
                createControlSelection(this, ranges);
            } else if (rangeCount) {
                this.addRange(ranges[0]);
            }
        };
    } else {
        module.fail("No means of selecting a Range or TextRange was found");
        return false;
    }

    selProto.getRangeAt = function(index) {
        if (index < 0 || index >= this.rangeCount) {
            throw new DOMException("INDEX_SIZE_ERR");
        } else {
            return this._ranges[index];
        }
    };

    var refreshSelection;

    if (useDocumentSelection) {
        refreshSelection = function(sel) {
            var range;
            if (api.isSelectionValid(sel.win)) {
                range = sel.docSelection.createRange();
            } else {
                range = dom.getBody(sel.win.document).createTextRange();
                range.collapse(true);
            }


            if (sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else if (isTextRange(range)) {
                updateFromTextRange(sel, range);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
        refreshSelection = function(sel) {
            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else {
                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
                if (sel.rangeCount) {
                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
                    }
                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
                    sel.isCollapsed = selectionIsCollapsed(sel);
                } else {
                    updateEmptySelection(sel);
                }
            }
        };
    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
        refreshSelection = function(sel) {
            var range, nativeSel = sel.nativeSelection;
            if (nativeSel.anchorNode) {
                range = getSelectionRangeAt(nativeSel, 0);
                sel._ranges = [range];
                sel.rangeCount = 1;
                updateAnchorAndFocusFromNativeSelection(sel);
                sel.isCollapsed = selectionIsCollapsed(sel);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else {
        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
        return false;
    }

    selProto.refresh = function(checkForChanges) {
        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
        refreshSelection(this);
        if (checkForChanges) {
            var i = oldRanges.length;
            if (i != this._ranges.length) {
                return false;
            }
            while (i--) {
                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
                    return false;
                }
            }
            return true;
        }
    };

    // Removal of a single range
    var removeRangeManually = function(sel, range) {
        var ranges = sel.getAllRanges(), removed = false;
        sel.removeAllRanges();
        for (var i = 0, len = ranges.length; i < len; ++i) {
            if (removed || range !== ranges[i]) {
                sel.addRange(ranges[i]);
            } else {
                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
                // times. removeRange should only remove the first instance, so the following ensures only the first
                // instance is removed
                removed = true;
            }
        }
        if (!sel.rangeCount) {
            updateEmptySelection(sel);
        }
    };

    if (implementsControlRange) {
        selProto.removeRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                var controlRange = this.docSelection.createRange();
                var rangeElement = getSingleElementFromRange(range);

                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
                // element contained by the supplied range
                var doc = dom.getDocument(controlRange.item(0));
                var newControlRange = dom.getBody(doc).createControlRange();
                var el, removed = false;
                for (var i = 0, len = controlRange.length; i < len; ++i) {
                    el = controlRange.item(i);
                    if (el !== rangeElement || removed) {
                        newControlRange.add(controlRange.item(i));
                    } else {
                        removed = true;
                    }
                }
                newControlRange.select();

                // Update the wrapped selection based on what's now in the native selection
                updateControlSelection(this);
            } else {
                removeRangeManually(this, range);
            }
        };
    } else {
        selProto.removeRange = function(range) {
            removeRangeManually(this, range);
        };
    }

    // Detecting if a selection is backwards
    var selectionIsBackwards;
    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
        selectionIsBackwards = function(sel) {
            var backwards = false;
            if (sel.anchorNode) {
                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
            }
            return backwards;
        };

        selProto.isBackwards = function() {
            return selectionIsBackwards(this);
        };
    } else {
        selectionIsBackwards = selProto.isBackwards = function() {
            return false;
        };
    }

    // Selection text
    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
    selProto.toString = function() {

        var rangeTexts = [];
        for (var i = 0, len = this.rangeCount; i < len; ++i) {
            rangeTexts[i] = "" + this._ranges[i];
        }
        return rangeTexts.join("");
    };

    function assertNodeInSameDocument(sel, node) {
        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
            throw new DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
    selProto.collapse = function(node, offset) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.collapseToPoint(node, offset);
        this.removeAllRanges();
        this.addRange(range);
        this.isCollapsed = true;
    };

    selProto.collapseToStart = function() {
        if (this.rangeCount) {
            var range = this._ranges[0];
            this.collapse(range.startContainer, range.startOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    selProto.collapseToEnd = function() {
        if (this.rangeCount) {
            var range = this._ranges[this.rangeCount - 1];
            this.collapse(range.endContainer, range.endOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
    // never used by Rangy.
    selProto.selectAllChildren = function(node) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.selectNodeContents(node);
        this.removeAllRanges();
        this.addRange(range);
    };

    selProto.deleteFromDocument = function() {
        // Sepcial behaviour required for Control selections
        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
            var controlRange = this.docSelection.createRange();
            var element;
            while (controlRange.length) {
                element = controlRange.item(0);
                controlRange.remove(element);
                element.parentNode.removeChild(element);
            }
            this.refresh();
        } else if (this.rangeCount) {
            var ranges = this.getAllRanges();
            this.removeAllRanges();
            for (var i = 0, len = ranges.length; i < len; ++i) {
                ranges[i].deleteContents();
            }
            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
            // range. Firefox moves the selection to where the final selected range was, so we emulate that
            this.addRange(ranges[len - 1]);
        }
    };

    // The following are non-standard extensions
    selProto.getAllRanges = function() {
        return this._ranges.slice(0);
    };

    selProto.setSingleRange = function(range) {
        this.setRanges( [range] );
    };

    selProto.containsNode = function(node, allowPartial) {
        for (var i = 0, len = this._ranges.length; i < len; ++i) {
            if (this._ranges[i].containsNode(node, allowPartial)) {
                return true;
            }
        }
        return false;
    };

    selProto.toHtml = function() {
        var html = "";
        if (this.rangeCount) {
            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
            for (var i = 0, len = this._ranges.length; i < len; ++i) {
                container.appendChild(this._ranges[i].cloneContents());
            }
            html = container.innerHTML;
        }
        return html;
    };

    function inspect(sel) {
        var rangeInspects = [];
        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

        if (typeof sel.rangeCount != "undefined") {
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
            }
        }
        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";

    }

    selProto.getName = function() {
        return "WrappedSelection";
    };

    selProto.inspect = function() {
        return inspect(this);
    };

    selProto.detach = function() {
        this.win[windowPropertyName] = null;
        this.win = this.anchorNode = this.focusNode = null;
    };

    WrappedSelection.inspect = inspect;

    api.Selection = WrappedSelection;

    api.selectionPrototype = selProto;

    api.addCreateMissingNativeApiListener(function(win) {
        if (typeof win.getSelection == "undefined") {
            win.getSelection = function() {
                return api.getSelection(this);
            };
        }
        win = null;
    });
});

define("rangy", (function (global) {
    return function () {
        var ret, fn;
        return ret || global.rangy;
    };
}(this)));

//     Underscore.js 1.5.1
//     http://underscorejs.org
//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Establish the object that gets returned to break out of a loop iteration.
  var breaker = {};

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    concat           = ArrayProto.concat,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeForEach      = ArrayProto.forEach,
    nativeMap          = ArrayProto.map,
    nativeReduce       = ArrayProto.reduce,
    nativeReduceRight  = ArrayProto.reduceRight,
    nativeFilter       = ArrayProto.filter,
    nativeEvery        = ArrayProto.every,
    nativeSome         = ArrayProto.some,
    nativeIndexOf      = ArrayProto.indexOf,
    nativeLastIndexOf  = ArrayProto.lastIndexOf,
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind;

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we're in
  // the browser, add `_` as a global object via a string identifier,
  // for Closure Compiler "advanced" mode.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  // Current version.
  _.VERSION = '1.5.1';

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles objects with the built-in `forEach`, arrays, and raw objects.
  // Delegates to **ECMAScript 5**'s native `forEach` if available.
  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

  // Return the results of applying the iterator to each element.
  // Delegates to **ECMAScript 5**'s native `map` if available.
  _.map = _.collect = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
    each(obj, function(value, index, list) {
      results.push(iterator.call(context, value, index, list));
    });
    return results;
  };

  var reduceError = 'Reduce of empty array with no initial value';

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduce && obj.reduce === nativeReduce) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
    }
    each(obj, function(value, index, list) {
      if (!initial) {
        memo = value;
        initial = true;
      } else {
        memo = iterator.call(context, memo, value, index, list);
      }
    });
    if (!initial) throw new TypeError(reduceError);
    return memo;
  };

  // The right-associative version of reduce, also known as `foldr`.
  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
    var initial = arguments.length > 2;
    if (obj == null) obj = [];
    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
      if (context) iterator = _.bind(iterator, context);
      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
    }
    var length = obj.length;
    if (length !== +length) {
      var keys = _.keys(obj);
      length = keys.length;
    }
    each(obj, function(value, index, list) {
      index = keys ? keys[--length] : --length;
      if (!initial) {
        memo = obj[index];
        initial = true;
      } else {
        memo = iterator.call(context, memo, obj[index], index, list);
      }
    });
    if (!initial) throw new TypeError(reduceError);
    return memo;
  };

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, iterator, context) {
    var result;
    any(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) {
        result = value;
        return true;
      }
    });
    return result;
  };

  // Return all the elements that pass a truth test.
  // Delegates to **ECMAScript 5**'s native `filter` if available.
  // Aliased as `select`.
  _.filter = _.select = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
    each(obj, function(value, index, list) {
      if (iterator.call(context, value, index, list)) results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, iterator, context) {
    return _.filter(obj, function(value, index, list) {
      return !iterator.call(context, value, index, list);
    }, context);
  };

  // Determine whether all of the elements match a truth test.
  // Delegates to **ECMAScript 5**'s native `every` if available.
  // Aliased as `all`.
  _.every = _.all = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = true;
    if (obj == null) return result;
    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
    each(obj, function(value, index, list) {
      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if at least one element in the object matches a truth test.
  // Delegates to **ECMAScript 5**'s native `some` if available.
  // Aliased as `any`.
  var any = _.some = _.any = function(obj, iterator, context) {
    iterator || (iterator = _.identity);
    var result = false;
    if (obj == null) return result;
    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
    each(obj, function(value, index, list) {
      if (result || (result = iterator.call(context, value, index, list))) return breaker;
    });
    return !!result;
  };

  // Determine if the array or object contains a given value (using `===`).
  // Aliased as `include`.
  _.contains = _.include = function(obj, target) {
    if (obj == null) return false;
    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
    return any(obj, function(value) {
      return value === target;
    });
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      return (isFunc ? method : value[method]).apply(value, args);
    });
  };

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, function(value){ return value[key]; });
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // containing specific `key:value` pairs.
  _.where = function(obj, attrs, first) {
    if (_.isEmpty(attrs)) return first ? void 0 : [];
    return _[first ? 'find' : 'filter'](obj, function(value) {
      for (var key in attrs) {
        if (attrs[key] !== value[key]) return false;
      }
      return true;
    });
  };

  // Convenience version of a common use case of `find`: getting the first object
  // containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.where(obj, attrs, true);
  };

  // Return the maximum element or (element-based computation).
  // Can't optimize arrays of integers longer than 65,535 elements.
  // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
  _.max = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.max.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return -Infinity;
    var result = {computed : -Infinity, value: -Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed > result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iterator, context) {
    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
      return Math.min.apply(Math, obj);
    }
    if (!iterator && _.isEmpty(obj)) return Infinity;
    var result = {computed : Infinity, value: Infinity};
    each(obj, function(value, index, list) {
      var computed = iterator ? iterator.call(context, value, index, list) : value;
      computed < result.computed && (result = {value : value, computed : computed});
    });
    return result.value;
  };

  // Shuffle an array.
  _.shuffle = function(obj) {
    var rand;
    var index = 0;
    var shuffled = [];
    each(obj, function(value) {
      rand = _.random(index++);
      shuffled[index - 1] = shuffled[rand];
      shuffled[rand] = value;
    });
    return shuffled;
  };

  // An internal function to generate lookup iterators.
  var lookupIterator = function(value) {
    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
  };

  // Sort the object's values by a criterion produced by an iterator.
  _.sortBy = function(obj, value, context) {
    var iterator = lookupIterator(value);
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value : value,
        index : index,
        criteria : iterator.call(context, value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index < right.index ? -1 : 1;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(obj, value, context, behavior) {
    var result = {};
    var iterator = lookupIterator(value == null ? _.identity : value);
    each(obj, function(value, index) {
      var key = iterator.call(context, value, index, obj);
      behavior(result, key, value);
    });
    return result;
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key, value) {
      (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
    });
  };

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = function(obj, value, context) {
    return group(obj, value, context, function(result, key) {
      if (!_.has(result, key)) result[key] = 0;
      result[key]++;
    });
  };

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iterator, context) {
    iterator = iterator == null ? _.identity : lookupIterator(iterator);
    var value = iterator.call(context, obj);
    var low = 0, high = array.length;
    while (low < high) {
      var mid = (low + high) >>> 1;
      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
    }
    return low;
  };

  // Safely create a real, live array from anything iterable.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (obj.length === +obj.length) return _.map(obj, _.identity);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
  };

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N. The **guard** check allows it to work with
  // `_.map`.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array. The **guard** check allows it to work with `_.map`.
  _.last = function(array, n, guard) {
    if (array == null) return void 0;
    if ((n != null) && !guard) {
      return slice.call(array, Math.max(array.length - n, 0));
    } else {
      return array[array.length - 1];
    }
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array. The **guard**
  // check allows it to work with `_.map`.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, (n == null) || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, _.identity);
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, output) {
    if (shallow && _.every(input, _.isArray)) {
      return concat.apply(output, input);
    }
    each(input, function(value) {
      if (_.isArray(value) || _.isArguments(value)) {
        shallow ? push.apply(output, value) : flatten(value, shallow, output);
      } else {
        output.push(value);
      }
    });
    return output;
  };

  // Return a completely flattened version of an array.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, []);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iterator, context) {
    if (_.isFunction(isSorted)) {
      context = iterator;
      iterator = isSorted;
      isSorted = false;
    }
    var initial = iterator ? _.map(array, iterator, context) : array;
    var results = [];
    var seen = [];
    each(initial, function(value, index) {
      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
        seen.push(value);
        results.push(array[index]);
      }
    });
    return results;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = function() {
    return _.uniq(_.flatten(arguments, true));
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    var rest = slice.call(arguments, 1);
    return _.filter(_.uniq(array), function(item) {
      return _.every(rest, function(other) {
        return _.indexOf(other, item) >= 0;
      });
    });
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = function(array) {
    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
    return _.filter(array, function(value){ return !_.contains(rest, value); });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    var length = _.max(_.pluck(arguments, "length").concat(0));
    var results = new Array(length);
    for (var i = 0; i < length; i++) {
      results[i] = _.pluck(arguments, '' + i);
    }
    return results;
  };

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values.
  _.object = function(list, values) {
    if (list == null) return {};
    var result = {};
    for (var i = 0, l = list.length; i < l; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
  // we need this function. Return the position of the first occurrence of an
  // item in an array, or -1 if the item is not included in the array.
  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = function(array, item, isSorted) {
    if (array == null) return -1;
    var i = 0, l = array.length;
    if (isSorted) {
      if (typeof isSorted == 'number') {
        i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
      } else {
        i = _.sortedIndex(array, item);
        return array[i] === item ? i : -1;
      }
    }
    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
    for (; i < l; i++) if (array[i] === item) return i;
    return -1;
  };

  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
  _.lastIndexOf = function(array, item, from) {
    if (array == null) return -1;
    var hasIndex = from != null;
    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
    }
    var i = (hasIndex ? from : array.length);
    while (i--) if (array[i] === item) return i;
    return -1;
  };

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (arguments.length <= 1) {
      stop = start || 0;
      start = 0;
    }
    step = arguments[2] || 1;

    var len = Math.max(Math.ceil((stop - start) / step), 0);
    var idx = 0;
    var range = new Array(len);

    while(idx < len) {
      range[idx++] = start;
      start += step;
    }

    return range;
  };

  // Function (ahem) Functions
  // ------------------

  // Reusable constructor function for prototype setting.
  var ctor = function(){};

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  // available.
  _.bind = function(func, context) {
    var args, bound;
    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError;
    args = slice.call(arguments, 2);
    return bound = function() {
      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
      ctor.prototype = func.prototype;
      var self = new ctor;
      ctor.prototype = null;
      var result = func.apply(self, args.concat(slice.call(arguments)));
      if (Object(result) === result) return result;
      return self;
    };
  };

  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context.
  _.partial = function(func) {
    var args = slice.call(arguments, 1);
    return function() {
      return func.apply(this, args.concat(slice.call(arguments)));
    };
  };

  // Bind all of an object's methods to that object. Useful for ensuring that
  // all callbacks defined on an object belong to it.
  _.bindAll = function(obj) {
    var funcs = slice.call(arguments, 1);
    if (funcs.length === 0) throw new Error("bindAll must be passed function names");
    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
    return obj;
  };

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memo = {};
    hasher || (hasher = _.identity);
    return function() {
      var key = hasher.apply(this, arguments);
      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
    };
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function(){ return func.apply(null, args); }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = function(func) {
    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  };

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    options || (options = {});
    var later = function() {
      previous = options.leading === false ? 0 : new Date;
      timeout = null;
      result = func.apply(context, args);
    };
    return function() {
      var now = new Date;
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0) {
        clearTimeout(timeout);
        timeout = null;
        previous = now;
        result = func.apply(context, args);
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var result;
    var timeout = null;
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if (!immediate) result = func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(context, args);
      return result;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return function() {
      var args = [func];
      push.apply(args, arguments);
      return wrapper.apply(this, args);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var funcs = arguments;
    return function() {
      var args = arguments;
      for (var i = funcs.length - 1; i >= 0; i--) {
        args = [funcs[i].apply(this, args)];
      }
      return args[0];
    };
  };

  // Returns a function that will only be executed after being called N times.
  _.after = function(times, func) {
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Object Functions
  // ----------------

  // Retrieve the names of an object's properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`
  _.keys = nativeKeys || function(obj) {
    if (obj !== Object(obj)) throw new TypeError('Invalid object');
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var values = [];
    for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
    return values;
  };

  // Convert an object into a list of `[key, value]` pairs.
  _.pairs = function(obj) {
    var pairs = [];
    for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      if (source) {
        for (var prop in source) {
          obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    each(keys, function(key) {
      if (key in obj) copy[key] = obj[key];
    });
    return copy;
  };

   // Return a copy of the object without the blacklisted properties.
  _.omit = function(obj) {
    var copy = {};
    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
    for (var key in obj) {
      if (!_.contains(keys, key)) copy[key] = obj[key];
    }
    return copy;
  };

  // Fill in a given object with default properties.
  _.defaults = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      if (source) {
        for (var prop in source) {
          if (obj[prop] === void 0) obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Internal recursive comparison function for `isEqual`.
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a == 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    if (a == null || b == null) return a === b;
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className != toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, dates, and booleans are compared by value.
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return a == String(b);
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
        // other numeric values.
        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a == +b;
      // RegExps are compared by their source patterns and flags.
      case '[object RegExp]':
        return a.source == b.source &&
               a.global == b.global &&
               a.multiline == b.multiline &&
               a.ignoreCase == b.ignoreCase;
    }
    if (typeof a != 'object' || typeof b != 'object') return false;
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] == a) return bStack[length] == b;
    }
    // Objects with different constructors are not equivalent, but `Object`s
    // from different frames are.
    var aCtor = a.constructor, bCtor = b.constructor;
    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
      return false;
    }
    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);
    var size = 0, result = true;
    // Recursively compare objects and arrays.
    if (className == '[object Array]') {
      // Compare array lengths to determine if a deep comparison is necessary.
      size = a.length;
      result = size == b.length;
      if (result) {
        // Deep compare the contents, ignoring non-numeric properties.
        while (size--) {
          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
        }
      }
    } else {
      // Deep compare objects.
      for (var key in a) {
        if (_.has(a, key)) {
          // Count the expected number of properties.
          size++;
          // Deep compare each member.
          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
        }
      }
      // Ensure that both objects contain the same number of properties.
      if (result) {
        for (key in b) {
          if (_.has(b, key) && !(size--)) break;
        }
        result = !size;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return result;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b, [], []);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
    for (var key in obj) if (_.has(obj, key)) return false;
    return true;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    return obj === Object(obj);
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return !!(obj && _.has(obj, 'callee'));
    };
  }

  // Optimize `isFunction` if appropriate.
  if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
      return typeof obj === 'function';
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  _.isNaN = function(obj) {
    return _.isNumber(obj) && obj != +obj;
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, key) {
    return hasOwnProperty.call(obj, key);
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iterators.
  _.identity = function(value) {
    return value;
  };

  // Run a function **n** times.
  _.times = function(n, iterator, context) {
    var accum = Array(Math.max(0, n));
    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
    return accum;
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
  };

  // List of HTML entities for escaping.
  var entityMap = {
    escape: {
      '&': '&',
      '<': '<',
      '>': '>',
      '"': '"',
      "'": ''',
      '/': '/'
    }
  };
  entityMap.unescape = _.invert(entityMap.escape);

  // Regexes containing the keys and values listed immediately above.
  var entityRegexes = {
    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
  };

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  _.each(['escape', 'unescape'], function(method) {
    _[method] = function(string) {
      if (string == null) return '';
      return ('' + string).replace(entityRegexes[method], function(match) {
        return entityMap[method][match];
      });
    };
  });

  // If the value of the named `property` is a function then invoke it with the
  // `object` as context; otherwise, return it.
  _.result = function(object, property) {
    if (object == null) return void 0;
    var value = object[property];
    return _.isFunction(value) ? value.call(object) : value;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    each(_.functions(obj), function(name){
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return result.call(this, func.apply(_, args));
      };
    });
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = ++idCounter + '';
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g,
    escape      : /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\t':     't',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  _.template = function(text, data, settings) {
    var render;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = new RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset)
        .replace(escaper, function(match) { return '\\' + escapes[match]; });

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      }
      if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      }
      if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }
      index = offset + match.length;
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + "return __p;\n";

    try {
      render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    if (data) return render(data, _);
    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled function source as a convenience for precompilation.
    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function, which will delegate to the wrapper.
  _.chain = function(obj) {
    return _(obj).chain();
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var result = function(obj) {
    return this._chain ? _(obj).chain() : obj;
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
      return result.call(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return result.call(this, method.apply(this._wrapped, arguments));
    };
  });

  _.extend(_.prototype, {

    // Start chaining a wrapped Underscore object.
    chain: function() {
      this._chain = true;
      return this;
    },

    // Extracts the result from a wrapped and chained object.
    value: function() {
      return this._wrapped;
    }

  });

}).call(this);

define("underscore", (function (global) {
    return function () {
        var ret, fn;
        return ret || global._;
    };
}(this)));

//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.
    
    However, the code's intention should be transparent.
    
    *** IE SUPPORT ***
    
    If you require this library to work in IE7, add the following after declaring crel.
    
    var testDiv = document.createElement('div'),
        testLabel = document.createElement('label');

    testDiv.setAttribute('class', 'a');    
    testDiv['className'] !== 'a' ? crel.attrMap['class'] = 'className':undefined;
    testDiv.setAttribute('name','a');
    testDiv['name'] !== 'a' ? crel.attrMap['name'] = function(element, value){
        element.id = value;
    }:undefined;
    

    testLabel.setAttribute('for', 'a');
    testLabel['htmlFor'] !== 'a' ? crel.attrMap['for'] = 'htmlFor':undefined;
    
    

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define('crel',factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    // based on http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
    var isNode = typeof Node === 'object'
        ? function (object) { return object instanceof Node }
        : function (object) {
            return object
                && typeof object === 'object'
                && typeof object.nodeType === 'number'
                && typeof object.nodeName === 'string';
        };

    function crel(){
        var document = window.document,
            args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = document.createElement(args[0]),
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel.attrMap;

        // shortcut
        if(argumentsLength === 1){
            return element;
        }

        if(typeof settings !== 'object' || isNode(settings)) {
            --childIndex;
            settings = null;
        }

        // shortcut if there is only one child that is a string    
        if((argumentsLength - childIndex) === 1 && typeof args[childIndex] === 'string' && element.textContent !== undefined){
            element.textContent = args[childIndex];
        }else{    
            for(; childIndex < argumentsLength; ++childIndex){
                child = args[childIndex];
                
                if(child == null){
                    continue;
                }
                
                if(!isNode(child)){
                    child = document.createTextNode(child);
                }
                
                element.appendChild(child);
            }
        }
        
        for(var key in settings){
            if(!attributeMap[key]){
                element.setAttribute(key, settings[key]);
            }else{
                var attr = crel.attrMap[key];
                if(typeof attr === 'function'){     
                    attr(element, settings[key]);               
                }else{            
                    element.setAttribute(attr, settings[key]);
                }
            }
        }
        
        return element;
    }
    
    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    // String referenced so that compilers maintain the property name.
    crel['attrMap'] = {};
    
    // String referenced so that compilers maintain the property name.
    crel["isNode"] = isNode;
    
    return crel;
}));

// Setup an empty localStorage or upgrade an existing one
define('storage',[
    "underscore"
], function(_) {

    function retrieveIndexArray(storeIndex) {
        try {
            return _.compact(localStorage[storeIndex].split(";"));
        }
        catch(e) {
            localStorage[storeIndex] = ";";
            return [];
        }
    }

    var fileIndexList = retrieveIndexArray("file.list");
    var currentFileIndex, settings;

    // localStorage versioning
    var version = localStorage.version;

    if(version === undefined) {

        // Not used anymore
        localStorage.removeItem("sync.queue");
        localStorage.removeItem("sync.current");
        localStorage.removeItem("file.counter");

        _.each(fileIndexList, function(fileIndex) {
            localStorage[fileIndex + ".publish"] = ";";
            var syncIndexList = retrieveIndexArray(fileIndex + ".sync");
            _.each(syncIndexList, function(syncIndex) {
                localStorage[syncIndex + ".contentCRC"] = "0";
                // We store title CRC only for Google Drive synchronization
                if(localStorage[syncIndex + ".etag"] !== undefined) {
                    localStorage[syncIndex + ".titleCRC"] = "0";
                }
            });
        });
        version = "v1";
    }

    if(version == "v1") {
        var gdriveLastChangeId = localStorage["sync.gdrive.lastChangeId"];
        if(gdriveLastChangeId) {
            localStorage["gdrive.lastChangeId"] = gdriveLastChangeId;
            localStorage.removeItem("sync.gdrive.lastChangeId");
        }
        var dropboxLastChangeId = localStorage["sync.dropbox.lastChangeId"];
        if(dropboxLastChangeId) {
            localStorage["dropbox.lastChangeId"] = dropboxLastChangeId;
            localStorage.removeItem("sync.dropbox.lastChangeId");
        }

        var PROVIDER_GDRIVE = "gdrive";
        var PROVIDER_DROPBOX = "dropbox";
        var SYNC_PROVIDER_GDRIVE = "sync." + PROVIDER_GDRIVE + ".";
        var SYNC_PROVIDER_DROPBOX = "sync." + PROVIDER_DROPBOX + ".";
        _.each(fileIndexList, function(fileIndex) {
            var syncIndexList = retrieveIndexArray(fileIndex + ".sync");
            _.each(syncIndexList, function(syncIndex) {
                var syncAttributes = {};
                if(syncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
                    syncAttributes.provider = PROVIDER_GDRIVE;
                    syncAttributes.id = syncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
                    syncAttributes.etag = localStorage[syncIndex + ".etag"];
                    syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"];
                    syncAttributes.titleCRC = localStorage[syncIndex + ".titleCRC"];
                }
                else if(syncIndex.indexOf(SYNC_PROVIDER_DROPBOX) === 0) {
                    syncAttributes.provider = PROVIDER_DROPBOX;
                    syncAttributes.path = decodeURIComponent(syncIndex.substring(SYNC_PROVIDER_DROPBOX.length));
                    syncAttributes.version = localStorage[syncIndex + ".version"];
                    syncAttributes.contentCRC = localStorage[syncIndex + ".contentCRC"];
                }
                localStorage[syncIndex] = JSON.stringify(syncAttributes);
                localStorage.removeItem(syncIndex + ".etag");
                localStorage.removeItem(syncIndex + ".version");
                localStorage.removeItem(syncIndex + ".contentCRC");
                localStorage.removeItem(syncIndex + ".titleCRC");
            });
        });
        version = "v2";
    }

    if(version == "v2") {
        _.each(fileIndexList, function(fileIndex) {
            if(!_.has(localStorage, fileIndex + ".sync")) {
                localStorage.removeItem(fileIndex + ".title");
                localStorage.removeItem(fileIndex + ".publish");
                localStorage.removeItem(fileIndex + ".content");
                localStorage["file.list"].replace(";" + fileIndex + ";", ";");
            }
        });
        version = "v3";
    }

    if(version == "v3") {
        currentFileIndex = localStorage["file.current"];
        if(currentFileIndex !== undefined && localStorage["file.list"].indexOf(";" + currentFileIndex + ";") === -1) {
            localStorage.removeItem("file.current");
        }
        version = "v4";
    }

    if(version == "v4") {
        // Recreate GitHub token
        localStorage.removeItem("githubToken");
        version = "v5";
    }

    if(version == "v5") {
        _.each(fileIndexList, function(fileIndex) {
            var publishIndexList = retrieveIndexArray(fileIndex + ".publish");
            _.each(publishIndexList, function(publishIndex) {
                var publishAttributes = JSON.parse(localStorage[publishIndex]);
                if(publishAttributes.provider == "gdrive") {
                    // Change fileId to Id to be consistent with syncAttributes
                    publishAttributes.id = publishAttributes.fileId;
                    publishAttributes.fileId = undefined;
                    localStorage[publishIndex] = JSON.stringify(publishAttributes);
                }
            });
        });
        version = "v6";
    }

    if(version == "v6") {
        currentFileIndex = localStorage["file.current"];
        if(currentFileIndex !== undefined) {
            localStorage[currentFileIndex + ".selectTime"] = new Date().getTime();
            localStorage.removeItem("file.current");
        }
        version = "v7";
    }

    if(version == "v7" || version == "v8" || version == "v9") {
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            delete settings.editorFontFamily;
            delete settings.editorFontSize;
            settings.template && (settings.template = settings.template.replace('http://benweet.github.io/stackedit/css/main-min.css', 'http://benweet.github.io/stackedit/res-min/themes/default.css'));
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v10";
    }

    if(version == "v10") {
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            ((settings.extensionSettings || {}).markdownExtra || {}).extensions && settings.extensionSettings.markdownExtra.extensions.push('smartypants');
            settings.sshProxy == 'http://stackedit-ssh-proxy.herokuapp.com/' && (settings.sshProxy = 'https://stackedit-ssh-proxy.herokuapp.com/');
            settings.template && (settings.template = settings.template.replace('http://benweet.github.io/stackedit/lib/', 'https://stackedit.io/libs/'));
            settings.template && (settings.template = settings.template.replace('http://benweet.github.io/stackedit/', 'https://stackedit.io/'));
            settings.pdfTemplate && (settings.pdfTemplate = settings.pdfTemplate.replace('http://benweet.github.io/stackedit/lib/', 'https://stackedit.io/libs/'));
            settings.pdfTemplate && (settings.pdfTemplate = settings.pdfTemplate.replace('http://benweet.github.io/stackedit/', 'https://stackedit.io/'));
            settings.defaultContent && (settings.defaultContent = settings.defaultContent.replace('http://benweet.github.io/stackedit/', 'https://stackedit.io/'));
            settings.commitMsg && (settings.commitMsg = settings.commitMsg.replace('http://benweet.github.io/stackedit/', 'https://stackedit.io/'));
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v11";
    }

    if(version == "v11") {
        // Force new theme by using themeV3 variable
        localStorage.removeItem("theme");
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            // Force new font
            delete settings.editorFontFamily;
            delete settings.editorFontSize;
            settings.template && (settings.template = settings.template.replace('https://stackedit.io/res-min/themes/default.css', 'https://stackedit.io/res-min/themes/base.css'));
            settings.pdfTemplate && (settings.pdfTemplate = settings.pdfTemplate.replace('https://stackedit.io/res-min/themes/default.css', 'https://stackedit.io/res-min/themes/base.css'));
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v12";
    }

    if(version == "v12" || version == "v13") {
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            // Have to reset the font because of Monaco issue with ACE
            delete settings.editorFontFamily;
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v14";
    }

    if(version == "v14") {
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            settings.template && (settings.template = settings.template.replace('https://stackedit.io/res-min/themes/default.css', 'https://stackedit.io/res-min/themes/base.css'));
            settings.pdfTemplate && (settings.pdfTemplate = settings.pdfTemplate.replace('https://stackedit.io/res-min/themes/default.css', 'https://stackedit.io/res-min/themes/base.css'));
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v15";
    }

    if(version == "v15") {
        localStorage.removeItem('gdrivePermissions');
        if(_.has(localStorage, 'gdrive.lastChangeId')) {
            localStorage['google.gdrive0.gdrive.lastChangeId'] = localStorage['gdrive.lastChangeId'];
            localStorage.removeItem('gdrive.lastChangeId');
        }
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            if(((settings.extensionSettings || {}).markdownExtra || {}).extensions) {
                settings.extensionSettings.markdownExtra.extensions.push('newlines');
                settings.extensionSettings.markdownExtra.extensions.push('strikethrough');
            }
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v16";
    }

    if(version == "v16" || version == "v17") {
        localStorage.removeItem('focusMode');
        localStorage.removeItem('mode');
        localStorage.removeItem('gdrive.state');
        localStorage.removeItem('google.picasa0.permissions');
        localStorage.removeItem('google.picasa0.userId');
        if(_.has(localStorage, 'settings')) {
            settings = JSON.parse(localStorage.settings);
            delete settings.shortcuts;
            delete settings.editorFontFamily;
            delete settings.editorFontSize;
            delete settings.maxWidth;
            localStorage.settings = JSON.stringify(settings);
        }
        version = "v18";
    }

	if(version == 'v18') {
		if(_.has(localStorage, 'settings')) {
			settings = JSON.parse(localStorage.settings);
			((settings.extensionSettings || {}).markdownExtra || {}).diagrams = true;
			localStorage.settings = JSON.stringify(settings);
		}
		version = "v19";
	}

	if(version == 'v19') {
		// Force new theme by using themeV4 variable
		localStorage.removeItem("themeV3");
		// Force welcome tour
		localStorage.removeItem("welcomeTour");
		if(_.has(localStorage, 'settings')) {
			settings = JSON.parse(localStorage.settings);
			// New web services
			delete settings.pdfTemplate;
			delete settings.pdfPageSize;
			delete settings.sshProxy;
			localStorage.settings = JSON.stringify(settings);
		}
		version = "v20";
	}

	if(version == 'v20') {
		if(_.has(localStorage, 'settings')) {
			settings = JSON.parse(localStorage.settings);
			// Force use of text/plain
			delete settings.markdownMimeType;
			localStorage.settings = JSON.stringify(settings);
		}
		version = "v21";
	}

	if(version == "v21") {
		if(_.has(localStorage, 'settings')) {
			settings = JSON.parse(localStorage.settings);
			settings.template && (settings.template = settings.template.replace('https://stackedit.io/libs/MathJax/', 'https://cdn.mathjax.org/mathjax/latest/'));
			settings.pdfTemplate && (settings.pdfTemplate = settings.pdfTemplate.replace('/libs/MathJax/', '/res/bower-libs/MathJax/'));
			localStorage.settings = JSON.stringify(settings);
		}
		version = "v22";
	}

	localStorage.version = version;
    return localStorage;
});

/*!
 * XRegExp-All 3.0.0-pre
 * 
 * Steven Levithan � 2012 MIT License
 */

// Module systems magic dance
;(function(definition) {
    // Don't turn on strict mode for this function, so it can assign to global
    var self;

    // RequireJS
    if (typeof define === 'function') {
        define('xregexp',definition);
    // CommonJS
    } else if (typeof exports === 'object') {
        self = definition();
        // Use Node.js's `module.exports`. This supports both `require('xregexp')` and
        // `require('xregexp').XRegExp`
        (typeof module === 'object' ? (module.exports = self) : exports).XRegExp = self;
    // ',
			'',
			'
<%= documentHTML %>
', '' ].join('\n'), pdfTemplate: [ '', '', '', '', '<%= documentTitle %>', '', '', '', '', '<%= documentHTML %>', '' ].join('\n'), pdfOptions: [ '{', ' "marginTop": 25,', ' "marginRight": 25,', ' "marginBottom": 25,', ' "marginLeft": 25,', ' "pageSize": "A4"', '}' ].join('\n'), couchdbUrl: constants.COUCHDB_URL, extensionSettings: {} }; try { _.extend(settings, JSON.parse(storage.settings)); } catch(e) { // Ignore parsing error } return settings; }); /*global define:false */ /** * Copyright 2013 Craig Campbell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Mousetrap is a simple keyboard shortcut library for Javascript with * no external dependencies * * @version 1.4.6 * @url craig.is/killing/mice */ (function(window, document, undefined) { /** * mapping of special keycodes to their corresponding keys * * everything in this dictionary cannot use keypress events * so it has to be here to map to the correct keycodes for * keyup/keydown events * * @type {Object} */ var _MAP = { 8: 'backspace', 9: 'tab', 13: 'enter', 16: 'shift', 17: 'ctrl', 18: 'alt', 20: 'capslock', 27: 'esc', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down', 45: 'ins', 46: 'del', 91: 'meta', 93: 'meta', 224: 'meta' }, /** * mapping for special characters so they can support * * this dictionary is only used incase you want to bind a * keyup or keydown event to one of these keys * * @type {Object} */ _KEYCODE_MAP = { 106: '*', 107: '+', 109: '-', 110: '.', 111 : '/', 186: ';', 187: '=', 188: ',', 189: '-', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\', 221: ']', 222: '\'' }, /** * this is a mapping of keys that require shift on a US keypad * back to the non shift equivelents * * this is so you can use keyup events with these keys * * note that this will only work reliably on US keyboards * * @type {Object} */ _SHIFT_MAP = { '~': '`', '!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0', '_': '-', '+': '=', ':': ';', '\"': '\'', '<': ',', '>': '.', '?': '/', '|': '\\' }, /** * this is a list of special strings you can use to map * to modifier keys when you specify your keyboard shortcuts * * @type {Object} */ _SPECIAL_ALIASES = { 'option': 'alt', 'command': 'meta', 'return': 'enter', 'escape': 'esc', 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' }, /** * variable to store the flipped version of _MAP from above * needed to check if we should use keypress or not when no action * is specified * * @type {Object|undefined} */ _REVERSE_MAP, /** * a list of all the callbacks setup via Mousetrap.bind() * * @type {Object} */ _callbacks = {}, /** * direct map of string combinations to callbacks used for trigger() * * @type {Object} */ _directMap = {}, /** * keeps track of what level each sequence is at since multiple * sequences can start out with the same sequence * * @type {Object} */ _sequenceLevels = {}, /** * variable to store the setTimeout call * * @type {null|number} */ _resetTimer, /** * temporary state where we will ignore the next keyup * * @type {boolean|string} */ _ignoreNextKeyup = false, /** * temporary state where we will ignore the next keypress * * @type {boolean} */ _ignoreNextKeypress = false, /** * are we currently inside of a sequence? * type of action ("keyup" or "keydown" or "keypress") or false * * @type {boolean|string} */ _nextExpectedAction = false; /** * loop through the f keys, f1 to f19 and add them to the map * programatically */ for (var i = 1; i < 20; ++i) { _MAP[111 + i] = 'f' + i; } /** * loop through to map numbers on the numeric keypad */ for (i = 0; i <= 9; ++i) { _MAP[i + 96] = i; } /** * cross browser add event method * * @param {Element|HTMLDocument} object * @param {string} type * @param {Function} callback * @returns void */ function _addEvent(object, type, callback) { if (object.addEventListener) { object.addEventListener(type, callback, false); return; } object.attachEvent('on' + type, callback); } /** * takes the event and returns the key character * * @param {Event} e * @return {string} */ function _characterFromEvent(e) { // for keypress events we should return the character as is if (e.type == 'keypress') { var character = String.fromCharCode(e.which); // if the shift key is not pressed then it is safe to assume // that we want the character to be lowercase. this means if // you accidentally have caps lock on then your key bindings // will continue to work // // the only side effect that might not be desired is if you // bind something like 'A' cause you want to trigger an // event when capital A is pressed caps lock will no longer // trigger the event. shift+a will though. if (!e.shiftKey) { character = character.toLowerCase(); } return character; } // for non keypress events the special maps are needed if (_MAP[e.which]) { return _MAP[e.which]; } if (_KEYCODE_MAP[e.which]) { return _KEYCODE_MAP[e.which]; } // if it is not in the special map // with keydown and keyup events the character seems to always // come in as an uppercase character whether you are pressing shift // or not. we should make sure it is always lowercase for comparisons return String.fromCharCode(e.which).toLowerCase(); } /** * checks if two arrays are equal * * @param {Array} modifiers1 * @param {Array} modifiers2 * @returns {boolean} */ function _modifiersMatch(modifiers1, modifiers2) { return modifiers1.sort().join(',') === modifiers2.sort().join(','); } /** * resets all sequence counters except for the ones passed in * * @param {Object} doNotReset * @returns void */ function _resetSequences(doNotReset) { doNotReset = doNotReset || {}; var activeSequences = false, key; for (key in _sequenceLevels) { if (doNotReset[key]) { activeSequences = true; continue; } _sequenceLevels[key] = 0; } if (!activeSequences) { _nextExpectedAction = false; } } /** * finds all callbacks that match based on the keycode, modifiers, * and action * * @param {string} character * @param {Array} modifiers * @param {Event|Object} e * @param {string=} sequenceName - name of the sequence we are looking for * @param {string=} combination * @param {number=} level * @returns {Array} */ function _getMatches(character, modifiers, e, sequenceName, combination, level) { var i, callback, matches = [], action = e.type; // if there are no events related to this keycode if (!_callbacks[character]) { return []; } // if a modifier key is coming up on its own we should allow it if (action == 'keyup' && _isModifier(character)) { modifiers = [character]; } // loop through all callbacks for the key that was pressed // and see if any of them match for (i = 0; i < _callbacks[character].length; ++i) { callback = _callbacks[character][i]; // if a sequence name is not specified, but this is a sequence at // the wrong level then move onto the next match if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { continue; } // if the action we are looking for doesn't match the action we got // then we should keep going if (action != callback.action) { continue; } // if this is a keypress event and the meta key and control key // are not pressed that means that we need to only look at the // character, otherwise check the modifiers as well // // chrome will not fire a keypress if meta or control is down // safari will fire a keypress if meta or meta+shift is down // firefox will fire a keypress if meta or control is down if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { // when you bind a combination or sequence a second time it // should overwrite the first one. if a sequenceName or // combination is specified in this call it does just that // // @todo make deleting its own method? var deleteCombo = !sequenceName && callback.combo == combination; var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; if (deleteCombo || deleteSequence) { _callbacks[character].splice(i, 1); } matches.push(callback); } } return matches; } /** * takes a key event and figures out what the modifiers are * * @param {Event} e * @returns {Array} */ function _eventModifiers(e) { var modifiers = []; if (e.shiftKey) { modifiers.push('shift'); } if (e.altKey) { modifiers.push('alt'); } if (e.ctrlKey) { modifiers.push('ctrl'); } if (e.metaKey) { modifiers.push('meta'); } return modifiers; } /** * prevents default for this event * * @param {Event} e * @returns void */ function _preventDefault(e) { if (e.preventDefault) { e.preventDefault(); return; } e.returnValue = false; } /** * stops propogation for this event * * @param {Event} e * @returns void */ function _stopPropagation(e) { if (e.stopPropagation) { e.stopPropagation(); return; } e.cancelBubble = true; } /** * actually calls the callback function * * if your callback function returns false this will use the jquery * convention - prevent default and stop propogation on the event * * @param {Function} callback * @param {Event} e * @returns void */ function _fireCallback(callback, e, combo, sequence) { // if this event should not happen stop here if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) { return; } if (callback(e, combo) === false) { _preventDefault(e); _stopPropagation(e); } } /** * handles a character key event * * @param {string} character * @param {Array} modifiers * @param {Event} e * @returns void */ function _handleKey(character, modifiers, e) { var callbacks = _getMatches(character, modifiers, e), i, doNotReset = {}, maxLevel = 0, processedSequenceCallback = false; // Calculate the maxLevel for sequences so we can only execute the longest callback sequence for (i = 0; i < callbacks.length; ++i) { if (callbacks[i].seq) { maxLevel = Math.max(maxLevel, callbacks[i].level); } } // loop through matching callbacks for this key event for (i = 0; i < callbacks.length; ++i) { // fire for all sequence callbacks // this is because if for example you have multiple sequences // bound such as "g i" and "g t" they both need to fire the // callback for matching g cause otherwise you can only ever // match the first one if (callbacks[i].seq) { // only fire callbacks for the maxLevel to prevent // subsequences from also firing // // for example 'a option b' should not cause 'option b' to fire // even though 'option b' is part of the other sequence // // any sequences that do not match here will be discarded // below by the _resetSequences call if (callbacks[i].level != maxLevel) { continue; } processedSequenceCallback = true; // keep a list of which sequences were matches for later doNotReset[callbacks[i].seq] = 1; _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); continue; } // if there were no sequence matches but we are still here // that means this is a regular match so we should fire that if (!processedSequenceCallback) { _fireCallback(callbacks[i].callback, e, callbacks[i].combo); } } // if the key you pressed matches the type of sequence without // being a modifier (ie "keyup" or "keypress") then we should // reset all sequences that were not matched by this event // // this is so, for example, if you have the sequence "h a t" and you // type "h e a r t" it does not match. in this case the "e" will // cause the sequence to reset // // modifier keys are ignored because you can have a sequence // that contains modifiers such as "enter ctrl+space" and in most // cases the modifier key will be pressed before the next key // // also if you have a sequence such as "ctrl+b a" then pressing the // "b" key will trigger a "keypress" and a "keydown" // // the "keydown" is expected when there is a modifier, but the // "keypress" ends up matching the _nextExpectedAction since it occurs // after and that causes the sequence to reset // // we ignore keypresses in a sequence that directly follow a keydown // for the same character var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { _resetSequences(doNotReset); } _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; } /** * handles a keydown event * * @param {Event} e * @returns void */ function _handleKeyEvent(e) { // normalize e.which for key events // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion if (typeof e.which !== 'number') { e.which = e.keyCode; } var character = _characterFromEvent(e); // no character found then stop if (!character) { return; } // need to use === for the character check because the character can be 0 if (e.type == 'keyup' && _ignoreNextKeyup === character) { _ignoreNextKeyup = false; return; } Mousetrap.handleKey(character, _eventModifiers(e), e); } /** * determines if the keycode specified is a modifier key or not * * @param {string} key * @returns {boolean} */ function _isModifier(key) { return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; } /** * called to set a 1 second timeout on the specified sequence * * this is so after each key press in the sequence you have 1 second * to press the next key before you have to start over * * @returns void */ function _resetSequenceTimer() { clearTimeout(_resetTimer); _resetTimer = setTimeout(_resetSequences, 1000); } /** * reverses the map lookup so that we can look for specific keys * to see what can and can't use keypress * * @return {Object} */ function _getReverseMap() { if (!_REVERSE_MAP) { _REVERSE_MAP = {}; for (var key in _MAP) { // pull out the numeric keypad from here cause keypress should // be able to detect the keys from the character if (key > 95 && key < 112) { continue; } if (_MAP.hasOwnProperty(key)) { _REVERSE_MAP[_MAP[key]] = key; } } } return _REVERSE_MAP; } /** * picks the best action based on the key combination * * @param {string} key - character for key * @param {Array} modifiers * @param {string=} action passed in */ function _pickBestAction(key, modifiers, action) { // if no action was picked in we should try to pick the one // that we think would work best for this key if (!action) { action = _getReverseMap()[key] ? 'keydown' : 'keypress'; } // modifier keys don't work as expected with keypress, // switch to keydown if (action == 'keypress' && modifiers.length) { action = 'keydown'; } return action; } /** * binds a key sequence to an event * * @param {string} combo - combo specified in bind call * @param {Array} keys * @param {Function} callback * @param {string=} action * @returns void */ function _bindSequence(combo, keys, callback, action) { // start off by adding a sequence level record for this combination // and setting the level to 0 _sequenceLevels[combo] = 0; /** * callback to increase the sequence level for this sequence and reset * all other sequences that were active * * @param {string} nextAction * @returns {Function} */ function _increaseSequence(nextAction) { return function() { _nextExpectedAction = nextAction; ++_sequenceLevels[combo]; _resetSequenceTimer(); }; } /** * wraps the specified callback inside of another function in order * to reset all sequence counters as soon as this sequence is done * * @param {Event} e * @returns void */ function _callbackAndReset(e) { _fireCallback(callback, e, combo); // we should ignore the next key up if the action is key down // or keypress. this is so if you finish a sequence and // release the key the final key will not trigger a keyup if (action !== 'keyup') { _ignoreNextKeyup = _characterFromEvent(e); } // weird race condition if a sequence ends with the key // another sequence begins with setTimeout(_resetSequences, 10); } // loop through keys one at a time and bind the appropriate callback // function. for any key leading up to the final one it should // increase the sequence. after the final, it should reset all sequences // // if an action is specified in the original bind call then that will // be used throughout. otherwise we will pass the action that the // next key in the sequence should match. this allows a sequence // to mix and match keypress and keydown events depending on which // ones are better suited to the key provided for (var i = 0; i < keys.length; ++i) { var isFinal = i + 1 === keys.length; var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); _bindSingle(keys[i], wrappedCallback, action, combo, i); } } /** * Converts from a string key combination to an array * * @param {string} combination like "command+shift+l" * @return {Array} */ function _keysFromString(combination) { if (combination === '+') { return ['+']; } return combination.split('+'); } /** * Gets info for a specific key combination * * @param {string} combination key combination ("command+s" or "a" or "*") * @param {string=} action * @returns {Object} */ function _getKeyInfo(combination, action) { var keys, key, i, modifiers = []; // take the keys from this pattern and figure out what the actual // pattern is all about keys = _keysFromString(combination); for (i = 0; i < keys.length; ++i) { key = keys[i]; // normalize key names if (_SPECIAL_ALIASES[key]) { key = _SPECIAL_ALIASES[key]; } // if this is not a keypress event then we should // be smart about using shift keys // this will only work for US keyboards however if (action && action != 'keypress' && _SHIFT_MAP[key]) { key = _SHIFT_MAP[key]; modifiers.push('shift'); } // if this key is a modifier then add it to the list of modifiers if (_isModifier(key)) { modifiers.push(key); } } // depending on what the key combination is // we will try to pick the best event for it action = _pickBestAction(key, modifiers, action); return { key: key, modifiers: modifiers, action: action }; } /** * binds a single keyboard combination * * @param {string} combination * @param {Function} callback * @param {string=} action * @param {string=} sequenceName - name of sequence if part of sequence * @param {number=} level - what part of the sequence the command is * @returns void */ function _bindSingle(combination, callback, action, sequenceName, level) { // store a direct mapped reference for use with Mousetrap.trigger _directMap[combination + ':' + action] = callback; // make sure multiple spaces in a row become a single space combination = combination.replace(/\s+/g, ' '); var sequence = combination.split(' '), info; // if this pattern is a sequence of keys then run through this method // to reprocess each pattern one key at a time if (sequence.length > 1) { _bindSequence(combination, sequence, callback, action); return; } info = _getKeyInfo(combination, action); // make sure to initialize array if this is the first time // a callback is added for this key _callbacks[info.key] = _callbacks[info.key] || []; // remove an existing match if there is one _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); // add this call back to the array // if it is a sequence put it at the beginning // if not put it at the end // // this is important because the way these are processed expects // the sequence ones to come first _callbacks[info.key][sequenceName ? 'unshift' : 'push']({ callback: callback, modifiers: info.modifiers, action: info.action, seq: sequenceName, level: level, combo: combination }); } /** * binds multiple combinations to the same callback * * @param {Array} combinations * @param {Function} callback * @param {string|undefined} action * @returns void */ function _bindMultiple(combinations, callback, action) { for (var i = 0; i < combinations.length; ++i) { _bindSingle(combinations[i], callback, action); } } // start! _addEvent(document, 'keypress', _handleKeyEvent); _addEvent(document, 'keydown', _handleKeyEvent); _addEvent(document, 'keyup', _handleKeyEvent); var Mousetrap = { /** * binds an event to mousetrap * * can be a single key, a combination of keys separated with +, * an array of keys, or a sequence of keys separated by spaces * * be sure to list the modifier keys first to make sure that the * correct key ends up getting bound (the last key in the pattern) * * @param {string|Array} keys * @param {Function} callback * @param {string=} action - 'keypress', 'keydown', or 'keyup' * @returns void */ bind: function(keys, callback, action) { keys = keys instanceof Array ? keys : [keys]; _bindMultiple(keys, callback, action); return this; }, /** * unbinds an event to mousetrap * * the unbinding sets the callback function of the specified key combo * to an empty function and deletes the corresponding key in the * _directMap dict. * * TODO: actually remove this from the _callbacks dictionary instead * of binding an empty function * * the keycombo+action has to be exactly the same as * it was defined in the bind method * * @param {string|Array} keys * @param {string} action * @returns void */ unbind: function(keys, action) { return Mousetrap.bind(keys, function() {}, action); }, /** * triggers an event that has already been bound * * @param {string} keys * @param {string=} action * @returns void */ trigger: function(keys, action) { if (_directMap[keys + ':' + action]) { _directMap[keys + ':' + action]({}, keys); } return this; }, /** * resets the library back to its initial state. this is useful * if you want to clear out the current keyboard shortcuts and bind * new ones - for example if you switch to another page * * @returns void */ reset: function() { _callbacks = {}; _directMap = {}; return this; }, /** * should we stop this event before firing off callbacks * * @param {Event} e * @param {Element} element * @return {boolean} */ stopCallback: function(e, element) { // if the element has the class "mousetrap" then no need to stop if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { return false; } // stop for input, select, and textarea return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; }, /** * exposes _handleKey publicly so it can be overwritten by extensions */ handleKey: _handleKey }; // expose mousetrap to the global object window.Mousetrap = Mousetrap; // expose mousetrap as an AMD module if (typeof define === 'function' && define.amd) { define('mousetrap',Mousetrap); } }) (window, document); define('logger',[], function () { // Defines the logger object var logger = { log: function () {}, info: function () {}, warn: function () {}, error: function () {} }; // We can run StackEdit with http://.../?console to print logs in the console return (/(\?|&)console($|&)/).test(location.search) ? console : logger; }); define('classes/Extension',[],function() { function Extension(extensionId, extensionName, isOptional, disableInViewer) { this.extensionId = extensionId; this.extensionName = extensionName; this.isOptional = isOptional; this.disableInViewer = disableInViewer; } return Extension; }); /* Copyright (c) 2010 Jeremy Faivre Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function(){ /** * Exception class thrown when an error occurs during parsing. * * @author Fabien Potencier * * @api */ /** * Constructor. * * @param string message The error message * @param integer parsedLine The line where the error occurred * @param integer snippet The snippet of code near the problem * @param string parsedFile The file name where the error occurred */ var YamlParseException = function(message, parsedLine, snippet, parsedFile){ this.rawMessage = message; this.parsedLine = (parsedLine !== undefined) ? parsedLine : -1; this.snippet = (snippet !== undefined) ? snippet : null; this.parsedFile = (parsedFile !== undefined) ? parsedFile : null; this.updateRepr(); this.message = message; }; YamlParseException.prototype = { name: 'YamlParseException', message: null, parsedFile: null, parsedLine: -1, snippet: null, rawMessage: null, isDefined: function(input) { return input != undefined && input != null; }, /** * Gets the snippet of code near the error. * * @return string The snippet of code */ getSnippet: function() { return this.snippet; }, /** * Sets the snippet of code near the error. * * @param string snippet The code snippet */ setSnippet: function(snippet) { this.snippet = snippet; this.updateRepr(); }, /** * Gets the filename where the error occurred. * * This method returns null if a string is parsed. * * @return string The filename */ getParsedFile: function() { return this.parsedFile; }, /** * Sets the filename where the error occurred. * * @param string parsedFile The filename */ setParsedFile: function(parsedFile) { this.parsedFile = parsedFile; this.updateRepr(); }, /** * Gets the line where the error occurred. * * @return integer The file line */ getParsedLine: function() { return this.parsedLine; }, /** * Sets the line where the error occurred. * * @param integer parsedLine The file line */ setParsedLine: function(parsedLine) { this.parsedLine = parsedLine; this.updateRepr(); }, updateRepr: function() { this.message = this.rawMessage; var dot = false; if ('.' === this.message.charAt(this.message.length - 1)) { this.message = this.message.substring(0, this.message.length - 1); dot = true; } if (null !== this.parsedFile) { this.message += ' in ' + JSON.stringify(this.parsedFile); } if (this.parsedLine >= 0) { this.message += ' at line ' + this.parsedLine; } if (this.snippet) { this.message += ' (near "' + this.snippet + '")'; } if (dot) { this.message += '.'; } } } /** * Yaml offers convenience methods to parse and dump YAML. * * @author Fabien Potencier * * @api */ var YamlRunningUnderNode = false; var Yaml = function(){}; Yaml.prototype = { /** * Parses YAML into a JS representation. * * The parse method, when supplied with a YAML stream (file), * will do its best to convert YAML in a file into a JS representation. * * Usage: * * obj = yaml.parseFile('config.yml'); * * * @param string input Path of YAML file * * @return array The YAML converted to a JS representation * * @throws YamlParseException If the YAML is not valid */ parseFile: function(file /* String */, callback /* Function */) { if ( callback == null ) { var input = this.getFileContents(file); var ret = null; try { ret = this.parse(input); } catch ( e ) { if ( e instanceof YamlParseException ) { e.setParsedFile(file); } throw e; } return ret; } this.getFileContents(file, function(data) { callback(new Yaml().parse(data)); }); }, /** * Parses YAML into a JS representation. * * The parse method, when supplied with a YAML stream (string), * will do its best to convert YAML into a JS representation. * * Usage: * * obj = yaml.parse(...); * * * @param string input string containing YAML * * @return array The YAML converted to a JS representation * * @throws YamlParseException If the YAML is not valid */ parse: function(input /* String */) { var yaml = new YamlParser(); return yaml.parse(input); }, /** * Dumps a JS representation to a YAML string. * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param array array JS representation * @param integer inline The level where you switch to inline YAML * * @return string A YAML string representing the original JS representation * * @api */ dump: function(array, inline, spaces) { if ( inline == null ) inline = 2; var yaml = new YamlDumper(); if (spaces) { yaml.numSpacesForIndentation = spaces; } return yaml.dump(array, inline); }, getXHR: function() { if ( window.XMLHttpRequest ) return new XMLHttpRequest(); if ( window.ActiveXObject ) { var names = [ "Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP" ]; for ( var i = 0; i < 4; i++ ) { try{ return new ActiveXObject(names[i]); } catch(e){} } } return null; }, getFileContents: function(file, callback) { if ( YamlRunningUnderNode ) { var fs = require('fs'); if ( callback == null ) { var data = fs.readFileSync(file); if (data == null) return null; return ''+data; } else { fs.readFile(file, function(err, data) { if (err) callback(null); else callback(data); }); } } else { var request = this.getXHR(); // Sync if ( callback == null ) { request.open('GET', file, false); request.send(null); if ( request.status == 200 || request.status == 0 ) return request.responseText; return null; } // Async request.onreadystatechange = function() { if ( request.readyState == 4 ) if ( request.status == 200 || request.status == 0 ) callback(request.responseText); else callback(null); }; request.open('GET', file, true); request.send(null); } } }; var YAML = { /* * @param integer inline The level where you switch to inline YAML */ stringify: function(input, inline, spaces) { return new Yaml().dump(input, inline, spaces); }, parse: function(input) { return new Yaml().parse(input); }, load: function(file, callback) { return new Yaml().parseFile(file, callback); } }; // Handle node.js case if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = YAML; YamlRunningUnderNode = true; // Add require handler (function () { var require_handler = function (module, filename) { // fill in result module.exports = YAML.load(filename); }; // register require extensions only if we're on node.js // hack for browserify if ( undefined !== require.extensions ) { require.extensions['.yml'] = require_handler; require.extensions['.yaml'] = require_handler; } }()); } } // Handle browser case if ( typeof(window) != "undefined" ) { window.YAML = YAML; } /** * YamlInline implements a YAML parser/dumper for the YAML inline syntax. */ var YamlInline = function(){}; YamlInline.prototype = { i: null, /** * Convert a YAML string to a JS object. * * @param string value A YAML string * * @return object A JS object representing the YAML string */ parse: function(value) { var result = null; value = this.trim(value); if ( 0 == value.length ) { return ''; } switch ( value.charAt(0) ) { case '[': result = this.parseSequence(value); break; case '{': result = this.parseMapping(value); break; default: result = this.parseScalar(value); } // some comment can end the scalar if ( value.substr(this.i+1).replace(/^\s*#.*$/, '') != '' ) { console.log("oups "+value.substr(this.i+1)); throw new YamlParseException('Unexpected characters near "'+value.substr(this.i)+'".'); } return result; }, /** * Dumps a given JS variable to a YAML string. * * @param mixed value The JS variable to convert * * @return string The YAML string representing the JS object */ dump: function(value) { if ( undefined == value || null == value ) return 'null'; if ( value instanceof Date) return value.toISOString(); if ( typeof(value) == 'object') return this.dumpObject(value); if ( typeof(value) == 'boolean' ) return value ? 'true' : 'false'; if ( /^\d+$/.test(value) ) return typeof(value) == 'string' ? "'"+value+"'" : parseInt(value); if ( this.isNumeric(value) ) return typeof(value) == 'string' ? "'"+value+"'" : parseFloat(value); if ( typeof(value) == 'number' ) return value == Infinity ? '.Inf' : ( value == -Infinity ? '-.Inf' : ( isNaN(value) ? '.NAN' : value ) ); var yaml = new YamlEscaper(); if ( yaml.requiresDoubleQuoting(value) ) return yaml.escapeWithDoubleQuotes(value); if ( yaml.requiresSingleQuoting(value) ) return yaml.escapeWithSingleQuotes(value); if ( '' == value ) return '""'; if ( this.getTimestampRegex().test(value) ) return "'"+value+"'"; if ( this.inArray(value.toLowerCase(), ['null','~','true','false']) ) return "'"+value+"'"; // default return value; }, /** * Dumps a JS object to a YAML string. * * @param object value The JS array to dump * * @return string The YAML string representing the JS object */ dumpObject: function(value) { var keys = this.getKeys(value); var output = null; var i; var len = keys.length; // array if ( value instanceof Array ) /*( 1 == len && '0' == keys[0] ) || ( len > 1 && this.reduceArray(keys, function(v,w){return Math.floor(v+w);}, 0) == len * (len - 1) / 2) )*/ { output = []; for ( i = 0; i < len; i++ ) { output.push(this.dump(value[keys[i]])); } return '['+output.join(', ')+']'; } // mapping output = []; for ( i = 0; i < len; i++ ) { output.push(this.dump(keys[i])+': '+this.dump(value[keys[i]])); } return '{ '+output.join(', ')+' }'; }, /** * Parses a scalar to a YAML string. * * @param scalar scalar * @param string delimiters * @param object stringDelimiters * @param integer i * @param boolean evaluate * * @return string A YAML string * * @throws YamlParseException When malformed inline YAML string is parsed */ parseScalar: function(scalar, delimiters, stringDelimiters, i, evaluate) { if ( delimiters == undefined ) delimiters = null; if ( stringDelimiters == undefined ) stringDelimiters = ['"', "'"]; if ( i == undefined ) i = 0; if ( evaluate == undefined ) evaluate = true; var output = null; var pos = null; var matches = null; if ( this.inArray(scalar[i], stringDelimiters) ) { // quoted scalar output = this.parseQuotedScalar(scalar, i); i = this.i; if (null !== delimiters) { var tmp = scalar.substr(i).replace(/^\s+/, ''); if (!this.inArray(tmp.charAt(0), delimiters)) { throw new YamlParseException('Unexpected characters ('+scalar.substr(i)+').'); } } } else { // "normal" string if ( !delimiters ) { output = (scalar+'').substring(i); i += output.length; // remove comments pos = output.indexOf(' #'); if ( pos != -1 ) { output = output.substr(0, pos).replace(/\s+$/g,''); } } else if ( matches = new RegExp('^(.+?)('+delimiters.join('|')+')').exec((scalar+'').substring(i)) ) { output = matches[1]; i += output.length; } else { throw new YamlParseException('Malformed inline YAML string ('+scalar+').'); } output = evaluate ? this.evaluateScalar(output) : output; } this.i = i; return output; }, /** * Parses a quoted scalar to YAML. * * @param string scalar * @param integer i * * @return string A YAML string * * @throws YamlParseException When malformed inline YAML string is parsed */ parseQuotedScalar: function(scalar, i) { var matches = null; //var item = /^(.*?)['"]\s*(?:[,:]|[}\]]\s*,)/.exec((scalar+'').substring(i))[1]; if ( !(matches = new RegExp('^'+YamlInline.REGEX_QUOTED_STRING).exec((scalar+'').substring(i))) ) { throw new YamlParseException('Malformed inline YAML string ('+(scalar+'').substring(i)+').'); } var output = matches[0].substr(1, matches[0].length - 2); var unescaper = new YamlUnescaper(); if ( '"' == (scalar+'').charAt(i) ) { output = unescaper.unescapeDoubleQuotedString(output); } else { output = unescaper.unescapeSingleQuotedString(output); } i += matches[0].length; this.i = i; return output; }, /** * Parses a sequence to a YAML string. * * @param string sequence * @param integer i * * @return string A YAML string * * @throws YamlParseException When malformed inline YAML string is parsed */ parseSequence: function(sequence, i) { if ( i == undefined ) i = 0; var output = []; var len = sequence.length; i += 1; // [foo, bar, ...] while ( i < len ) { switch ( sequence.charAt(i) ) { case '[': // nested sequence output.push(this.parseSequence(sequence, i)); i = this.i; break; case '{': // nested mapping output.push(this.parseMapping(sequence, i)); i = this.i; break; case ']': this.i = i; return output; case ',': case ' ': break; default: var isQuoted = this.inArray(sequence.charAt(i), ['"', "'"]); var value = this.parseScalar(sequence, [',', ']'], ['"', "'"], i); i = this.i; if ( !isQuoted && (value+'').indexOf(': ') != -1 ) { // embedded mapping? try { value = this.parseMapping('{'+value+'}'); } catch ( e ) { if ( !(e instanceof YamlParseException ) ) throw e; // no, it's not } } output.push(value); i--; } i++; } throw new YamlParseException('Malformed inline YAML string "'+sequence+'"'); }, /** * Parses a mapping to a YAML string. * * @param string mapping * @param integer i * * @return string A YAML string * * @throws YamlParseException When malformed inline YAML string is parsed */ parseMapping: function(mapping, i) { if ( i == undefined ) i = 0; var output = {}; var len = mapping.length; i += 1; var done = false; var doContinue = false; // {foo: bar, bar:foo, ...} while ( i < len ) { doContinue = false; switch ( mapping.charAt(i) ) { case ' ': case ',': i++; doContinue = true; break; case '}': this.i = i; return output; } if ( doContinue ) continue; // key var key = this.parseScalar(mapping, [':', ' '], ['"', "'"], i, false); i = this.i; // value done = false; while ( i < len ) { switch ( mapping.charAt(i) ) { case '[': // nested sequence output[key] = this.parseSequence(mapping, i); i = this.i; done = true; break; case '{': // nested mapping output[key] = this.parseMapping(mapping, i); i = this.i; done = true; break; case ':': case ' ': break; default: output[key] = this.parseScalar(mapping, [',', '}'], ['"', "'"], i); i = this.i; done = true; i--; } ++i; if ( done ) { doContinue = true; break; } } if ( doContinue ) continue; } throw new YamlParseException('Malformed inline YAML string "'+mapping+'"'); }, /** * Evaluates scalars and replaces magic values. * * @param string scalar * * @return string A YAML string */ evaluateScalar: function(scalar) { scalar = this.trim(scalar); var raw = null; var cast = null; if ( ( 'null' == scalar.toLowerCase() ) || ( '' == scalar ) || ( '~' == scalar ) ) return null; if ( (scalar+'').indexOf('!str ') == 0 ) return (''+scalar).substring(5); if ( (scalar+'').indexOf('! ') == 0 ) return parseInt(this.parseScalar((scalar+'').substr(2))); if ( /^\d+$/.test(scalar) ) { raw = scalar; cast = parseInt(scalar); return '0' == scalar.charAt(0) ? this.octdec(scalar) : (( ''+raw == ''+cast ) ? cast : raw); } if ( 'true' == (scalar+'').toLowerCase() ) return true; if ( 'false' == (scalar+'').toLowerCase() ) return false; if ( this.isNumeric(scalar) ) return '0x' == (scalar+'').substr(0, 2) ? this.hexdec(scalar) : parseFloat(scalar); if ( scalar.toLowerCase() == '.inf' ) return Infinity; if ( scalar.toLowerCase() == '.nan' ) return NaN; if ( scalar.toLowerCase() == '-.inf' ) return -Infinity; if ( /^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(scalar) ) return parseFloat(scalar.split(',').join('')); if ( this.getTimestampRegex().test(scalar) ) return new Date(this.strtotime(scalar)); //else return ''+scalar; }, /** * Gets a regex that matches an unix timestamp * * @return string The regular expression */ getTimestampRegex: function() { return new RegExp('^'+ '([0-9][0-9][0-9][0-9])'+ '-([0-9][0-9]?)'+ '-([0-9][0-9]?)'+ '(?:(?:[Tt]|[ \t]+)'+ '([0-9][0-9]?)'+ ':([0-9][0-9])'+ ':([0-9][0-9])'+ '(?:\.([0-9]*))?'+ '(?:[ \t]*(Z|([-+])([0-9][0-9]?)'+ '(?::([0-9][0-9]))?))?)?'+ '$','gi'); }, trim: function(str /* String */) { return (str+'').replace(/^\s+/,'').replace(/\s+$/,''); }, isNumeric: function(input) { return (input - 0) == input && input.length > 0 && input.replace(/\s+/g,'') != ''; }, inArray: function(key, tab) { var i; var len = tab.length; for ( i = 0; i < len; i++ ) { if ( key == tab[i] ) return true; } return false; }, getKeys: function(tab) { var ret = []; for ( var name in tab ) { if ( tab.hasOwnProperty(name) ) { ret.push(name); } } return ret; }, /*reduceArray: function(tab, fun) { var len = tab.length; if (typeof fun != "function") throw new YamlParseException("fun is not a function"); // no value to return if no initial value and an empty array if (len == 0 && arguments.length == 1) throw new YamlParseException("empty array"); var i = 0; if (arguments.length >= 2) { var rv = arguments[1]; } else { do { if (i in tab) { rv = tab[i++]; break; } // if array contains no values, no initial value to return if (++i >= len) throw new YamlParseException("no initial value to return"); } while (true); } for (; i < len; i++) { if (i in tab) rv = fun.call(null, rv, tab[i], i, tab); } return rv; },*/ octdec: function(input) { return parseInt((input+'').replace(/[^0-7]/gi, ''), 8); }, hexdec: function(input) { input = this.trim(input); if ( (input+'').substr(0, 2) == '0x' ) input = (input+'').substring(2); return parseInt((input+'').replace(/[^a-f0-9]/gi, ''), 16); }, /** * @see http://phpjs.org/functions/strtotime * @note we need timestamp with msecs so /1000 removed * @note original contained binary | 0 (wtf?!) everywhere, which messes everything up */ strtotime: function (h,b){var f,c,g,k,d="";h=(h+"").replace(/\s{2,}|^\s|\s$/g," ").replace(/[\t\r\n]/g,"");if(h==="now"){return b===null||isNaN(b)?new Date().getTime()||0:b||0}else{if(!isNaN(d=Date.parse(h))){return d||0}else{if(b){b=new Date(b)}else{b=new Date()}}}h=h.toLowerCase();var e={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]};var a=function(i){var o=(i[2]&&i[2]==="ago");var n=(n=i[0]==="last"?-1:1)*(o?-1:1);switch(i[0]){case"last":case"next":switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break;case"mon":if(i[1]==="month"){b.setMonth(b.getMonth()+n);break}default:var l=e.day[i[1].substring(0,3)];if(typeof l!=="undefined"){var p=l-b.getDay();if(p===0){p=7*n}else{if(p>0){if(i[0]==="last"){p-=7}}else{if(i[0]==="next"){p+=7}}}b.setDate(b.getDate()+p);b.setHours(0,0,0,0)}}break;default:if(/\d+/.test(i[0])){n*=parseInt(i[0],10);switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"mon":b.setMonth(b.getMonth()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break}}else{return false}break}return true};g=h.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(g!==null){if(!g[2]){g[2]="00:00:00"}else{if(!g[3]){g[2]+=":00"}}k=g[1].split(/-/g);k[1]=e.mon[k[1]-1]||k[1];k[0]=+k[0];k[0]=(k[0]>=0&&k[0]<=69)?"20"+(k[0]<10?"0"+k[0]:k[0]+""):(k[0]>=70&&k[0]<=99)?"19"+k[0]:k[0]+"";return parseInt(this.strtotime(k[2]+" "+k[1]+" "+k[0]+" "+g[2])+(g[4]?g[4]:""),10)}var j="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";g=h.match(new RegExp(j,"gi"));if(g===null){return false}for(f=0,c=g.length;f= newIndent ) { data.push(this.currentLine.substr(newIndent)); } else if ( 0 == indent ) { this.moveToPreviousLine(); break; } else { throw new YamlParseException('Indentation problem B', this.getRealCurrentLineNb() + 1, this.currentLine); } } return data.join("\n"); }, /** * Moves the parser to the next line. * * @return Boolean */ moveToNextLine: function() { if ( this.currentLineNb >= this.lines.length - 1 ) { return false; } this.currentLineNb++; this.currentLine = this.lines[this.currentLineNb]; return true; }, /** * Moves the parser to the previous line. */ moveToPreviousLine: function() { this.currentLineNb--; this.currentLine = this.lines[this.currentLineNb]; }, /** * Parses a YAML value. * * @param string value A YAML value * * @return mixed A JS value * * @throws YamlParseException When reference does not exist */ parseValue: function(value) { if ( '*' == (value+'').charAt(0) ) { if ( this.trim(value).charAt(0) == '#' ) { value = (value+'').substr(1, value.indexOf('#') - 2); } else { value = (value+'').substr(1); } if ( this.refs[value] == undefined ) { throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine); } return this.refs[value]; } var matches = null; if ( matches = /^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(value) ) { matches = {separator: matches[1], modifiers: matches[2], comments: matches[3]}; var modifiers = this.isDefined(matches.modifiers) ? matches.modifiers : ''; return this.parseFoldedScalar(matches.separator, modifiers.replace(/\d+/g, ''), Math.abs(parseInt(modifiers))); } try { return new YamlInline().parse(value); } catch (e) { if ( e instanceof YamlParseException ) { e.setParsedLine(this.getRealCurrentLineNb() + 1); e.setSnippet(this.currentLine); } throw e; } }, /** * Parses a folded scalar. * * @param string separator The separator that was used to begin this folded scalar (| or >) * @param string indicator The indicator that was used to begin this folded scalar (+ or -) * @param integer indentation The indentation that was used to begin this folded scalar * * @return string The text value */ parseFoldedScalar: function(separator, indicator, indentation) { if ( indicator == undefined ) indicator = ''; if ( indentation == undefined ) indentation = 0; separator = '|' == separator ? "\n" : ' '; var text = ''; var diff = null; var notEOF = this.moveToNextLine(); while ( notEOF && this.isCurrentLineBlank() ) { text += "\n"; notEOF = this.moveToNextLine(); } if ( !notEOF ) { return ''; } var matches = null; if ( !(matches = new RegExp('^('+(indentation ? this.strRepeat(' ', indentation) : ' +')+')(.*)$').exec(this.currentLine)) ) { this.moveToPreviousLine(); return ''; } matches = {indent: matches[1], text: matches[2]}; var textIndent = matches.indent; var previousIndent = 0; text += matches.text + separator; while ( this.currentLineNb + 1 < this.lines.length ) { this.moveToNextLine(); if ( matches = new RegExp('^( {'+textIndent.length+',})(.+)$').exec(this.currentLine) ) { matches = {indent: matches[1], text: matches[2]}; if ( ' ' == separator && previousIndent != matches.indent ) { text = text.substr(0, text.length - 1)+"\n"; } previousIndent = matches.indent; diff = matches.indent.length - textIndent.length; text += this.strRepeat(' ', diff) + matches.text + (diff != 0 ? "\n" : separator); } else if ( matches = /^( *)$/.exec(this.currentLine) ) { text += matches[1].replace(new RegExp('^ {1,'+textIndent.length+'}','g'), '')+"\n"; } else { this.moveToPreviousLine(); break; } } if ( ' ' == separator ) { // replace last separator by a newline text = text.replace(/ (\n*)$/g, "\n$1"); } switch ( indicator ) { case '': text = text.replace(/\n+$/g, "\n"); break; case '+': break; case '-': text = text.replace(/\n+$/g, ''); break; } return text; }, /** * Returns true if the next line is indented. * * @return Boolean Returns true if the next line is indented, false otherwise */ isNextLineIndented: function() { var currentIndentation = this.getCurrentLineIndentation(); var notEOF = this.moveToNextLine(); while ( notEOF && this.isCurrentLineEmpty() ) { notEOF = this.moveToNextLine(); } if ( false == notEOF ) { return false; } var ret = false; if ( this.getCurrentLineIndentation() <= currentIndentation ) { ret = true; } this.moveToPreviousLine(); return ret; }, /** * Returns true if the current line is blank or if it is a comment line. * * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise */ isCurrentLineEmpty: function() { return this.isCurrentLineBlank() || this.isCurrentLineComment(); }, /** * Returns true if the current line is blank. * * @return Boolean Returns true if the current line is blank, false otherwise */ isCurrentLineBlank: function() { return '' == this.trim(this.currentLine); }, /** * Returns true if the current line is a comment line. * * @return Boolean Returns true if the current line is a comment line, false otherwise */ isCurrentLineComment: function() { //checking explicitly the first char of the trim is faster than loops or strpos var ltrimmedLine = this.currentLine.replace(/^ +/g, ''); return ltrimmedLine.charAt(0) == '#'; }, /** * Cleanups a YAML string to be parsed. * * @param string value The input YAML string * * @return string A cleaned up YAML string */ cleanup: function(value) { value = value.split("\r\n").join("\n").split("\r").join("\n"); if ( !/\n$/.test(value) ) { value += "\n"; } // strip YAML header var count = 0; var regex = /^\%YAML[: ][\d\.]+.*\n/; while ( regex.test(value) ) { value = value.replace(regex, ''); count++; } this.offset += count; // remove leading comments regex = /^(#.*?\n)+/; if ( regex.test(value) ) { var trimmedValue = value.replace(regex, ''); // items have been removed, update the offset this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n"); value = trimmedValue; } // remove start of the document marker (---) regex = /^\-\-\-.*?\n/; if ( regex.test(value) ) { trimmedValue = value.replace(regex, ''); // items have been removed, update the offset this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n"); value = trimmedValue; // remove end of the document marker (...) value = value.replace(/\.\.\.\s*$/g, ''); } return value; }, /** * Returns true if the next line starts unindented collection * * @return Boolean Returns true if the next line starts unindented collection, false otherwise */ isNextLineUnIndentedCollection: function() { var currentIndentation = this.getCurrentLineIndentation(); var notEOF = this.moveToNextLine(); while (notEOF && this.isCurrentLineEmpty()) { notEOF = this.moveToNextLine(); } if (false === notEOF) { return false; } var ret = false; if ( this.getCurrentLineIndentation() == currentIndentation && this.isStringUnIndentedCollectionItem(this.currentLine) ) { ret = true; } this.moveToPreviousLine(); return ret; }, /** * Returns true if the string is unindented collection item * * @return Boolean Returns true if the string is unindented collection item, false otherwise */ isStringUnIndentedCollectionItem: function(string) { return (0 === this.currentLine.indexOf('- ')); }, isObject: function(input) { return typeof(input) == 'object' && this.isDefined(input); }, isEmpty: function(input) { return input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false; }, isDefined: function(input) { return input != undefined && input != null; }, reverseArray: function(input /* Array */) { var result = []; var len = input.length; for ( var i = len-1; i >= 0; i-- ) { result.push(input[i]); } return result; }, merge: function(a /* Object */, b /* Object */) { var c = {}; var i; for ( i in a ) { if ( a.hasOwnProperty(i) ) if ( /^\d+$/.test(i) ) c.push(a); else c[i] = a[i]; } for ( i in b ) { if ( b.hasOwnProperty(i) ) if ( /^\d+$/.test(i) ) c.push(b); else c[i] = b[i]; } return c; }, strRepeat: function(str /* String */, count /* Integer */) { var i; var result = ''; for ( i = 0; i < count; i++ ) result += str; return result; }, subStrCount: function(string, subString, start, length) { var c = 0; string = '' + string; subString = '' + subString; if ( start != undefined ) string = string.substr(start); if ( length != undefined ) string = string.substr(0, length); var len = string.length; var sublen = subString.length; for ( var i = 0; i < len; i++ ) { if ( subString == string.substr(i, sublen) ) c++; i += sublen - 1; } return c; }, trim: function(str /* String */) { return (str+'').replace(/^ +/,'').replace(/ +$/,''); } }; /** * YamlEscaper encapsulates escaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ YamlEscaper = function(){}; YamlEscaper.prototype = { /** * Determines if a JS value would require double quoting in YAML. * * @param string value A JS value * * @return Boolean True if the value would require double quotes. */ requiresDoubleQuoting: function(value) { return new RegExp(YamlEscaper.REGEX_CHARACTER_TO_ESCAPE).test(value); }, /** * Escapes and surrounds a JS value with double quotes. * * @param string value A JS value * * @return string The quoted, escaped string */ escapeWithDoubleQuotes: function(value) { value = value + ''; var len = YamlEscaper.escapees.length; var maxlen = YamlEscaper.escaped.length; var esc = YamlEscaper.escaped; for (var i = 0; i < len; ++i) if ( i >= maxlen ) esc.push(''); var ret = ''; ret = value.replace(new RegExp(YamlEscaper.escapees.join('|'),'g'), function(str){ for(var i = 0; i < len; ++i){ if( str == YamlEscaper.escapees[i] ) return esc[i]; } }); return '"' + ret + '"'; }, /** * Determines if a JS value would require single quoting in YAML. * * @param string value A JS value * * @return Boolean True if the value would require single quotes. */ requiresSingleQuoting: function(value) { return /[\s'":{}[\],&*#?]|^[-?|<>=!%@`]/.test(value); }, /** * Escapes and surrounds a JS value with single quotes. * * @param string value A JS value * * @return string The quoted, escaped string */ escapeWithSingleQuotes : function(value) { return "'" + value.replace(/'/g, "''") + "'"; } }; // Characters that would cause a dumped string to require double quoting. YamlEscaper.REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping. YamlEscaper.escapees = ['\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"]; YamlEscaper.escaped = ['\\"', '\\\\', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"]; /** * YamlUnescaper encapsulates unescaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ var YamlUnescaper = function(){}; YamlUnescaper.prototype = { /** * Unescapes a single quoted string. * * @param string value A single quoted string. * * @return string The unescaped string. */ unescapeSingleQuotedString: function(value) { return value.replace(/''/g, "'"); }, /** * Unescapes a double quoted string. * * @param string value A double quoted string. * * @return string The unescaped string. */ unescapeDoubleQuotedString: function(value) { var callback = function(m) { return new YamlUnescaper().unescapeCharacter(m); }; // evaluate the string return value.replace(new RegExp(YamlUnescaper.REGEX_ESCAPED_CHARACTER, 'g'), callback); }, /** * Unescapes a character that was found in a double-quoted string * * @param string value An escaped character * * @return string The unescaped character */ unescapeCharacter: function(value) { switch (value.charAt(1)) { case '0': return String.fromCharCode(0); case 'a': return String.fromCharCode(7); case 'b': return String.fromCharCode(8); case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return String.fromCharCode(11); case 'f': return String.fromCharCode(12); case 'r': return String.fromCharCode(13); case 'e': return "\x1b"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': // U+0085 NEXT LINE return "\x00\x85"; case '_': // U+00A0 NO-BREAK SPACE return "\x00\xA0"; case 'L': // U+2028 LINE SEPARATOR return "\x20\x28"; case 'P': // U+2029 PARAGRAPH SEPARATOR return "\x20\x29"; case 'x': return this.pack('n', new YamlInline().hexdec(value.substr(2, 2))); case 'u': return this.pack('n', new YamlInline().hexdec(value.substr(2, 4))); case 'U': return this.pack('N', new YamlInline().hexdec(value.substr(2, 8))); } }, /** * @see http://phpjs.org/functions/pack * @warning only modes used above copied */ pack: function(B){var g=0,o=1,m="",l="",z=0,p=[],E,s,C,I,h,c;var d,b,x,H,u,e,A,q,D,t,w,a,G,F,y,v,f;while(g(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;case"N":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z>24&255);m+=String.fromCharCode(arguments[o]>>16&255);m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;default:throw new Error("Warning: pack() Type "+E+": unknown format code")}}if(o */ var YamlDumper = function(){}; YamlDumper.prototype = { /** * Dumps a JS value to YAML. * * @param mixed input The JS value * @param integer inline The level where you switch to inline YAML * @param integer indent The level o indentation indentation (used internally) * * @return string The YAML representation of the JS value */ dump: function(input, inline, indent) { if ( inline == null ) inline = 0; if ( indent == null ) indent = 0; var output = ''; var prefix = indent ? this.strRepeat(' ', indent) : ''; var yaml; if (!this.numSpacesForIndentation) this.numSpacesForIndentation = 2; if ( inline <= 0 || !this.isObject(input) || this.isEmpty(input) ) { yaml = new YamlInline(); output += prefix + yaml.dump(input); } else { var isAHash = !this.arrayEquals(this.getKeys(input), this.range(0,input.length - 1)); var willBeInlined; for ( var key in input ) { if ( input.hasOwnProperty(key) ) { willBeInlined = inline - 1 <= 0 || !this.isObject(input[key]) || this.isEmpty(input[key]); if ( isAHash ) yaml = new YamlInline(); output += prefix + '' + (isAHash ? yaml.dump(key)+':' : '-') + '' + (willBeInlined ? ' ' : "\n") + '' + this.dump(input[key], inline - 1, (willBeInlined ? 0 : indent + this.numSpacesForIndentation)) + '' + (willBeInlined ? "\n" : ''); } } } return output; }, strRepeat: function(str /* String */, count /* Integer */) { var i; var result = ''; for ( i = 0; i < count; i++ ) result += str; return result; }, isObject: function(input) { return this.isDefined(input) && typeof(input) == 'object'; }, isEmpty: function(input) { var ret = input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false; if ( !ret && typeof(input) == "object" && !(input instanceof Array)){ var propCount = 0; for ( var key in input ) if ( input.hasOwnProperty(key) ) propCount++; ret = !propCount; } return ret; }, isDefined: function(input) { return input != undefined && input != null; }, getKeys: function(tab) { var ret = []; for ( var name in tab ) { if ( tab.hasOwnProperty(name) ) { ret.push(name); } } return ret; }, range: function(start, end) { if ( start > end ) return []; var ret = []; for ( var i = start; i <= end; i++ ) { ret.push(i); } return ret; }, arrayEquals: function(a,b) { if ( a.length != b.length ) return false; var len = a.length; for ( var i = 0; i < len; i++ ) { if ( a[i] != b[i] ) return false; } return true; } }; })(); define("yaml-js", (function (global) { return function () { var ret, fn; return ret || global.YAML; }; }(this))); define('extensions/yamlFrontMatterParser',[ "underscore", "classes/Extension", "yaml-js", ], function(_, Extension, YAML) { var yamlFrontMatterParser = new Extension("yamlFrontMatterParser", "YAML front matter"); var eventMgr; yamlFrontMatterParser.onEventMgrCreated = function(eventMgrParameter) { eventMgr = eventMgrParameter; }; var fileDesc; yamlFrontMatterParser.onFileSelected = function(fileDescParam) { fileDesc = fileDescParam; }; var regex = /^(\s*-{3}\s*\n([\w\W]+?)\n\s*-{3}\s*?\n)?([\w\W]*)$/; function parseFrontMatter(fileDescParam, content) { if(fileDescParam !== fileDesc) { return; } var results = regex.exec(content); var frontMatter = results[1]; var yaml = results[2]; if(!yaml) { fileDesc.frontMatter = undefined; } else if(!fileDesc.frontMatter || fileDesc.frontMatter._frontMatter != frontMatter) { fileDesc.frontMatter = undefined; try { fileDesc.frontMatter = YAML.parse(yaml); if(!_.isObject(fileDesc.frontMatter)) { fileDesc.frontMatter = undefined; } fileDesc.frontMatter._yaml = yaml; fileDesc.frontMatter._frontMatter = frontMatter; } catch (e) { } } } yamlFrontMatterParser.onFileOpen = parseFrontMatter; yamlFrontMatterParser.onContentChanged = parseFrontMatter; return yamlFrontMatterParser; }); /** * @license RequireJS text 2.0.13 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/requirejs/text for details */ /*jslint regexp: true */ /*global require, XMLHttpRequest, ActiveXObject, define, window, process, Packages, java, location, Components, FileUtils */ define('text',['module'], function (module) { var text, fs, Cc, Ci, xpcIsWindows, progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, hasLocation = typeof location !== 'undefined' && location.href, defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), defaultHostName = hasLocation && location.hostname, defaultPort = hasLocation && (location.port || undefined), buildMap = {}, masterConfig = (module.config && module.config()) || {}; text = { version: '2.0.13', strip: function (content) { //Strips declarations so that external SVG and XML //documents can be added to a document without worry. Also, if the string //is an HTML document, only the part inside the body tag is returned. if (content) { content = content.replace(xmlRegExp, ""); var matches = content.match(bodyRegExp); if (matches) { content = matches[1]; } } else { content = ""; } return content; }, jsEscape: function (content) { return content.replace(/(['\\])/g, '\\$1') .replace(/[\f]/g, "\\f") .replace(/[\b]/g, "\\b") .replace(/[\n]/g, "\\n") .replace(/[\t]/g, "\\t") .replace(/[\r]/g, "\\r") .replace(/[\u2028]/g, "\\u2028") .replace(/[\u2029]/g, "\\u2029"); }, createXhr: masterConfig.createXhr || function () { //Would love to dump the ActiveX crap in here. Need IE 6 to die first. var xhr, i, progId; if (typeof XMLHttpRequest !== "undefined") { return new XMLHttpRequest(); } else if (typeof ActiveXObject !== "undefined") { for (i = 0; i < 3; i += 1) { progId = progIds[i]; try { xhr = new ActiveXObject(progId); } catch (e) {} if (xhr) { progIds = [progId]; // so faster next time break; } } } return xhr; }, /** * Parses a resource name into its component parts. Resource names * look like: module/name.ext!strip, where the !strip part is * optional. * @param {String} name the resource name * @returns {Object} with properties "moduleName", "ext" and "strip" * where strip is a boolean. */ parseName: function (name) { var modName, ext, temp, strip = false, index = name.lastIndexOf("."), isRelative = name.indexOf('./') === 0 || name.indexOf('../') === 0; if (index !== -1 && (!isRelative || index > 1)) { modName = name.substring(0, index); ext = name.substring(index + 1); } else { modName = name; } temp = ext || modName; index = temp.indexOf("!"); if (index !== -1) { //Pull off the strip arg. strip = temp.substring(index + 1) === "strip"; temp = temp.substring(0, index); if (ext) { ext = temp; } else { modName = temp; } } return { moduleName: modName, ext: ext, strip: strip }; }, xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, /** * Is an URL on another domain. Only works for browser use, returns * false in non-browser environments. Only used to know if an * optimized .js version of a text resource should be loaded * instead. * @param {String} url * @returns Boolean */ useXhr: function (url, protocol, hostname, port) { var uProtocol, uHostName, uPort, match = text.xdRegExp.exec(url); if (!match) { return true; } uProtocol = match[2]; uHostName = match[3]; uHostName = uHostName.split(':'); uPort = uHostName[1]; uHostName = uHostName[0]; return (!uProtocol || uProtocol === protocol) && (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && ((!uPort && !uHostName) || uPort === port); }, finishLoad: function (name, strip, content, onLoad) { content = strip ? text.strip(content) : content; if (masterConfig.isBuild) { buildMap[name] = content; } onLoad(content); }, load: function (name, req, onLoad, config) { //Name has format: some.module.filext!strip //The strip part is optional. //if strip is present, then that means only get the string contents //inside a body tag in an HTML string. For XML/SVG content it means //removing the declarations so the content can be inserted //into the current doc without problems. // Do not bother with the work if a build and text will // not be inlined. if (config && config.isBuild && !config.inlineText) { onLoad(); return; } masterConfig.isBuild = config && config.isBuild; var parsed = text.parseName(name), nonStripName = parsed.moduleName + (parsed.ext ? '.' + parsed.ext : ''), url = req.toUrl(nonStripName), useXhr = (masterConfig.useXhr) || text.useXhr; // Do not load if it is an empty: url if (url.indexOf('empty:') === 0) { onLoad(); return; } //Load the text. Use XHR if possible and in a browser. if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { text.get(url, function (content) { text.finishLoad(name, parsed.strip, content, onLoad); }, function (err) { if (onLoad.error) { onLoad.error(err); } }); } else { //Need to fetch the resource across domains. Assume //the resource has been optimized into a JS module. Fetch //by the module name + extension, but do not include the //!strip part to avoid file system issues. req([nonStripName], function (content) { text.finishLoad(parsed.moduleName + '.' + parsed.ext, parsed.strip, content, onLoad); }); } }, write: function (pluginName, moduleName, write, config) { if (buildMap.hasOwnProperty(moduleName)) { var content = text.jsEscape(buildMap[moduleName]); write.asModule(pluginName + "!" + moduleName, "define(function () { return '" + content + "';});\n"); } }, writeFile: function (pluginName, moduleName, req, write, config) { var parsed = text.parseName(moduleName), extPart = parsed.ext ? '.' + parsed.ext : '', nonStripName = parsed.moduleName + extPart, //Use a '.js' file name so that it indicates it is a //script that can be loaded across domains. fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; //Leverage own load() method to load plugin value, but only //write out values that do not have the strip argument, //to avoid any potential issues with ! in file names. text.load(nonStripName, req, function (value) { //Use own write() method to construct full module value. //But need to create shell that translates writeFile's //write() to the right interface. var textWrite = function (contents) { return write(fileName, contents); }; textWrite.asModule = function (moduleName, contents) { return write.asModule(moduleName, fileName, contents); }; text.write(pluginName, nonStripName, textWrite, config); }, config); } }; if (masterConfig.env === 'node' || (!masterConfig.env && typeof process !== "undefined" && process.versions && !!process.versions.node && !process.versions['node-webkit'])) { //Using special require.nodeRequire, something added by r.js. // fs = require.nodeRequire('fs'); text.get = function (url, callback, errback) { try { var file = fs.readFileSync(url, 'utf8'); //Remove BOM (Byte Mark Order) from utf8 files if it is there. if (file[0] === '\uFEFF') { file = file.substring(1); } callback(file); } catch (e) { if (errback) { errback(e); } } }; } else if (masterConfig.env === 'xhr' || (!masterConfig.env && text.createXhr())) { text.get = function (url, callback, errback, headers) { var xhr = text.createXhr(), header; xhr.open('GET', url, true); //Allow plugins direct access to xhr headers if (headers) { for (header in headers) { if (headers.hasOwnProperty(header)) { xhr.setRequestHeader(header.toLowerCase(), headers[header]); } } } //Allow overrides specified in config if (masterConfig.onXhr) { masterConfig.onXhr(xhr, url); } xhr.onreadystatechange = function (evt) { var status, err; //Do not explicitly handle errors, those should be //visible via console output in the browser. if (xhr.readyState === 4) { status = xhr.status || 0; if (status > 399 && status < 600) { //An http 4xx or 5xx error. Signal an error. err = new Error(url + ' HTTP status: ' + status); err.xhr = xhr; if (errback) { errback(err); } } else { callback(xhr.responseText); } if (masterConfig.onXhrComplete) { masterConfig.onXhrComplete(xhr, url); } } }; xhr.send(null); }; } else if (masterConfig.env === 'rhino' || (!masterConfig.env && typeof Packages !== 'undefined' && typeof java !== 'undefined')) { //Why Java, why is this so awkward? text.get = function (url, callback) { var stringBuffer, line, encoding = "utf-8", file = new java.io.File(url), lineSeparator = java.lang.System.getProperty("line.separator"), input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), content = ''; try { stringBuffer = new java.lang.StringBuffer(); line = input.readLine(); // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 // http://www.unicode.org/faq/utf_bom.html // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 if (line && line.length() && line.charAt(0) === 0xfeff) { // Eat the BOM, since we've already found the encoding on this file, // and we plan to concatenating this buffer with others; the BOM should // only appear at the top of a file. line = line.substring(1); } if (line !== null) { stringBuffer.append(line); } while ((line = input.readLine()) !== null) { stringBuffer.append(lineSeparator); stringBuffer.append(line); } //Make sure we return a JavaScript string and not a Java string. content = String(stringBuffer.toString()); //String } finally { input.close(); } callback(content); }; } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && typeof Components !== 'undefined' && Components.classes && Components.interfaces)) { //Avert your gaze! Cc = Components.classes; Ci = Components.interfaces; Components.utils['import']('resource://gre/modules/FileUtils.jsm'); xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); text.get = function (url, callback) { var inStream, convertStream, fileObj, readData = {}; if (xpcIsWindows) { url = url.replace(/\//g, '\\'); } fileObj = new FileUtils.File(url); //XPCOM, you so crazy try { inStream = Cc['@mozilla.org/network/file-input-stream;1'] .createInstance(Ci.nsIFileInputStream); inStream.init(fileObj, 1, 0, false); convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] .createInstance(Ci.nsIConverterInputStream); convertStream.init(inStream, "utf-8", inStream.available(), Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); convertStream.readString(inStream.available(), readData); convertStream.close(); inStream.close(); callback(readData.value); } catch (e) { throw new Error((fileObj && fileObj.path || '') + ': ' + e); } }; } return text; }); define('text!html/markdownExtraSettingsBlock.html',[],function () { return '

Adds extra features to the original Markdown syntax.

\n
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n\t
\n\t\t\n\t\t
\n\t\t\t\n\t\t
\n\t
\n
\nMore info';}); // Copyright (C) 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview * some functions for browser-side pretty printing of code contained in html. * *

* For a fairly comprehensive set of languages see the * README * file that came with this source. At a minimum, the lexer should work on a * number of languages including C and friends, Java, Python, Bash, SQL, HTML, * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk * and a subset of Perl, but, because of commenting conventions, doesn't work on * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. *

* Usage:

    *
  1. include this source file in an html page via * {@code } *
  2. define style rules. See the example page for examples. *
  3. mark the {@code
    } and {@code } tags in your source with
     *    {@code class=prettyprint.}
     *    You can also use the (html deprecated) {@code } tag, but the pretty
     *    printer needs to do more substantial DOM manipulations to support that, so
     *    some css styles may not be preserved.
     * </ol>
     * That's it.  I wanted to keep the API as simple as possible, so there's no
     * need to specify which language the code is in, but if you wish, you can add
     * another class to the {@code <pre>} or {@code <code>} element to specify the
     * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
     * starts with "lang-" followed by a file extension, specifies the file type.
     * See the "lang-*.js" files in this directory for code that implements
     * per-language file handlers.
     * <p>
     * Change log:<br>
     * cbeust, 2006/08/22
     * <blockquote>
     *   Java annotations (start with "@") are now captured as literals ("lit")
     * </blockquote>
     * @requires console
     */
    
    // JSLint declarations
    /*global console, document, navigator, setTimeout, window, define */
    
    /** @define {boolean} */
    var IN_GLOBAL_SCOPE = true;
    
    /**
     * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
     * UI events.
     * If set to {@code false}, {@code prettyPrint()} is synchronous.
     */
    window['PR_SHOULD_USE_CONTINUATION'] = true;
    
    /**
     * Pretty print a chunk of code.
     * @param {string} sourceCodeHtml The HTML to pretty print.
     * @param {string} opt_langExtension The language name to use.
     *     Typically, a filename extension like 'cpp' or 'java'.
     * @param {number|boolean} opt_numberLines True to number lines,
     *     or the 1-indexed number of the first line in sourceCodeHtml.
     * @return {string} code as html, but prettier
     */
    var prettyPrintOne;
    /**
     * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
     * {@code class=prettyprint} and prettify them.
     *
     * @param {Function} opt_whenDone called when prettifying is done.
     * @param {HTMLElement|HTMLDocument} opt_root an element or document
     *   containing all the elements to pretty print.
     *   Defaults to {@code document.body}.
     */
    var prettyPrint;
    
    
    (function () {
      var win = window;
      // Keyword lists for various languages.
      // We use things that coerce to strings to make them compact when minified
      // and to defeat aggressive optimizers that fold large string constants.
      var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
      var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + 
          "double,enum,extern,float,goto,inline,int,long,register,short,signed," +
          "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
      var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
          "new,operator,private,protected,public,this,throw,true,try,typeof"];
      var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
          "concept,concept_map,const_cast,constexpr,decltype,delegate," +
          "dynamic_cast,explicit,export,friend,generic,late_check," +
          "mutable,namespace,nullptr,property,reinterpret_cast,static_assert," +
          "static_cast,template,typeid,typename,using,virtual,where"];
      var JAVA_KEYWORDS = [COMMON_KEYWORDS,
          "abstract,assert,boolean,byte,extends,final,finally,implements,import," +
          "instanceof,interface,null,native,package,strictfp,super,synchronized," +
          "throws,transient"];
      var CSHARP_KEYWORDS = [JAVA_KEYWORDS,
          "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
          "fixed,foreach,from,group,implicit,in,internal,into,is,let," +
          "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," +
          "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," +
          "var,virtual,where"];
      var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
          "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
          "throw,true,try,unless,until,when,while,yes";
      var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
          "debugger,eval,export,function,get,null,set,undefined,var,with," +
          "Infinity,NaN"];
      var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
          "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
          "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
      var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
          "elif,except,exec,finally,from,global,import,in,is,lambda," +
          "nonlocal,not,or,pass,print,raise,try,with,yield," +
          "False,True,None"];
      var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
          "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
          "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
          "BEGIN,END"];
       var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "as,assert,const,copy,drop," +
          "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv," +
          "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"];
      var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
          "function,in,local,set,then,until"];
      var ALL_KEYWORDS = [
          CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,
          PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
      var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
    
      // token style names.  correspond to css classes
      /**
       * token style for a string literal
       * @const
       */
      var PR_STRING = 'str';
      /**
       * token style for a keyword
       * @const
       */
      var PR_KEYWORD = 'kwd';
      /**
       * token style for a comment
       * @const
       */
      var PR_COMMENT = 'com';
      /**
       * token style for a type
       * @const
       */
      var PR_TYPE = 'typ';
      /**
       * token style for a literal value.  e.g. 1, null, true.
       * @const
       */
      var PR_LITERAL = 'lit';
      /**
       * token style for a punctuation string.
       * @const
       */
      var PR_PUNCTUATION = 'pun';
      /**
       * token style for plain text.
       * @const
       */
      var PR_PLAIN = 'pln';
    
      /**
       * token style for an sgml tag.
       * @const
       */
      var PR_TAG = 'tag';
      /**
       * token style for a markup declaration such as a DOCTYPE.
       * @const
       */
      var PR_DECLARATION = 'dec';
      /**
       * token style for embedded source.
       * @const
       */
      var PR_SOURCE = 'src';
      /**
       * token style for an sgml attribute name.
       * @const
       */
      var PR_ATTRIB_NAME = 'atn';
      /**
       * token style for an sgml attribute value.
       * @const
       */
      var PR_ATTRIB_VALUE = 'atv';
    
      /**
       * A class that indicates a section of markup that is not code, e.g. to allow
       * embedding of line numbers within code listings.
       * @const
       */
      var PR_NOCODE = 'nocode';
    
      
      
      /**
       * A set of tokens that can precede a regular expression literal in
       * javascript
       * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
       * has the full list, but I've removed ones that might be problematic when
       * seen in languages that don't support regular expression literals.
       *
       * <p>Specifically, I've removed any keywords that can't precede a regexp
       * literal in a syntactically legal javascript program, and I've removed the
       * "in" keyword since it's not a keyword in many languages, and might be used
       * as a count of inches.
       *
       * <p>The link above does not accurately describe EcmaScript rules since
       * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
       * very well in practice.
       *
       * @private
       * @const
       */
      var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
      
      // CAVEAT: this does not properly handle the case where a regular
      // expression immediately follows another since a regular expression may
      // have flags for case-sensitivity and the like.  Having regexp tokens
      // adjacent is not valid in any language I'm aware of, so I'm punting.
      // TODO: maybe style special characters inside a regexp as punctuation.
    
      /**
       * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
       * matches the union of the sets of strings matched by the input RegExp.
       * Since it matches globally, if the input strings have a start-of-input
       * anchor (/^.../), it is ignored for the purposes of unioning.
       * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
       * @return {RegExp} a global regex.
       */
      function combinePrefixPatterns(regexs) {
        var capturedGroupIndex = 0;
      
        var needToFoldCase = false;
        var ignoreCase = false;
        for (var i = 0, n = regexs.length; i < n; ++i) {
          var regex = regexs[i];
          if (regex.ignoreCase) {
            ignoreCase = true;
          } else if (/[a-z]/i.test(regex.source.replace(
                         /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
            needToFoldCase = true;
            ignoreCase = false;
            break;
          }
        }
      
        var escapeCharToCodeUnit = {
          'b': 8,
          't': 9,
          'n': 0xa,
          'v': 0xb,
          'f': 0xc,
          'r': 0xd
        };
      
        function decodeEscape(charsetPart) {
          var cc0 = charsetPart.charCodeAt(0);
          if (cc0 !== 92 /* \\ */) {
            return cc0;
          }
          var c1 = charsetPart.charAt(1);
          cc0 = escapeCharToCodeUnit[c1];
          if (cc0) {
            return cc0;
          } else if ('0' <= c1 && c1 <= '7') {
            return parseInt(charsetPart.substring(1), 8);
          } else if (c1 === 'u' || c1 === 'x') {
            return parseInt(charsetPart.substring(2), 16);
          } else {
            return charsetPart.charCodeAt(1);
          }
        }
      
        function encodeEscape(charCode) {
          if (charCode < 0x20) {
            return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
          }
          var ch = String.fromCharCode(charCode);
          return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
              ? "\\" + ch : ch;
        }
      
        function caseFoldCharset(charSet) {
          var charsetParts = charSet.substring(1, charSet.length - 1).match(
              new RegExp(
                  '\\\\u[0-9A-Fa-f]{4}'
                  + '|\\\\x[0-9A-Fa-f]{2}'
                  + '|\\\\[0-3][0-7]{0,2}'
                  + '|\\\\[0-7]{1,2}'
                  + '|\\\\[\\s\\S]'
                  + '|-'
                  + '|[^-\\\\]',
                  'g'));
          var ranges = [];
          var inverse = charsetParts[0] === '^';
      
          var out = ['['];
          if (inverse) { out.push('^'); }
      
          for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
            var p = charsetParts[i];
            if (/\\[bdsw]/i.test(p)) {  // Don't muck with named groups.
              out.push(p);
            } else {
              var start = decodeEscape(p);
              var end;
              if (i + 2 < n && '-' === charsetParts[i + 1]) {
                end = decodeEscape(charsetParts[i + 2]);
                i += 2;
              } else {
                end = start;
              }
              ranges.push([start, end]);
              // If the range might intersect letters, then expand it.
              // This case handling is too simplistic.
              // It does not deal with non-latin case folding.
              // It works for latin source code identifiers though.
              if (!(end < 65 || start > 122)) {
                if (!(end < 65 || start > 90)) {
                  ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
                }
                if (!(end < 97 || start > 122)) {
                  ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
                }
              }
            }
          }
      
          // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
          // -> [[1, 12], [14, 14], [16, 17]]
          ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
          var consolidatedRanges = [];
          var lastRange = [];
          for (var i = 0; i < ranges.length; ++i) {
            var range = ranges[i];
            if (range[0] <= lastRange[1] + 1) {
              lastRange[1] = Math.max(lastRange[1], range[1]);
            } else {
              consolidatedRanges.push(lastRange = range);
            }
          }
      
          for (var i = 0; i < consolidatedRanges.length; ++i) {
            var range = consolidatedRanges[i];
            out.push(encodeEscape(range[0]));
            if (range[1] > range[0]) {
              if (range[1] + 1 > range[0]) { out.push('-'); }
              out.push(encodeEscape(range[1]));
            }
          }
          out.push(']');
          return out.join('');
        }
      
        function allowAnywhereFoldCaseAndRenumberGroups(regex) {
          // Split into character sets, escape sequences, punctuation strings
          // like ('(', '(?:', ')', '^'), and runs of characters that do not
          // include any of the above.
          var parts = regex.source.match(
              new RegExp(
                  '(?:'
                  + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
                  + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
                  + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
                  + '|\\\\[0-9]+'  // a back-reference or octal escape
                  + '|\\\\[^ux0-9]'  // other escape sequence
                  + '|\\(\\?[:!=]'  // start of a non-capturing group
                  + '|[\\(\\)\\^]'  // start/end of a group, or line start
                  + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
                  + ')',
                  'g'));
          var n = parts.length;
      
          // Maps captured group numbers to the number they will occupy in
          // the output or to -1 if that has not been determined, or to
          // undefined if they need not be capturing in the output.
          var capturedGroups = [];
      
          // Walk over and identify back references to build the capturedGroups
          // mapping.
          for (var i = 0, groupIndex = 0; i < n; ++i) {
            var p = parts[i];
            if (p === '(') {
              // groups are 1-indexed, so max group index is count of '('
              ++groupIndex;
            } else if ('\\' === p.charAt(0)) {
              var decimalValue = +p.substring(1);
              if (decimalValue) {
                if (decimalValue <= groupIndex) {
                  capturedGroups[decimalValue] = -1;
                } else {
                  // Replace with an unambiguous escape sequence so that
                  // an octal escape sequence does not turn into a backreference
                  // to a capturing group from an earlier regex.
                  parts[i] = encodeEscape(decimalValue);
                }
              }
            }
          }
      
          // Renumber groups and reduce capturing groups to non-capturing groups
          // where possible.
          for (var i = 1; i < capturedGroups.length; ++i) {
            if (-1 === capturedGroups[i]) {
              capturedGroups[i] = ++capturedGroupIndex;
            }
          }
          for (var i = 0, groupIndex = 0; i < n; ++i) {
            var p = parts[i];
            if (p === '(') {
              ++groupIndex;
              if (!capturedGroups[groupIndex]) {
                parts[i] = '(?:';
              }
            } else if ('\\' === p.charAt(0)) {
              var decimalValue = +p.substring(1);
              if (decimalValue && decimalValue <= groupIndex) {
                parts[i] = '\\' + capturedGroups[decimalValue];
              }
            }
          }
      
          // Remove any prefix anchors so that the output will match anywhere.
          // ^^ really does mean an anchored match though.
          for (var i = 0; i < n; ++i) {
            if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
          }
      
          // Expand letters to groups to handle mixing of case-sensitive and
          // case-insensitive patterns if necessary.
          if (regex.ignoreCase && needToFoldCase) {
            for (var i = 0; i < n; ++i) {
              var p = parts[i];
              var ch0 = p.charAt(0);
              if (p.length >= 2 && ch0 === '[') {
                parts[i] = caseFoldCharset(p);
              } else if (ch0 !== '\\') {
                // TODO: handle letters in numeric escapes.
                parts[i] = p.replace(
                    /[a-zA-Z]/g,
                    function (ch) {
                      var cc = ch.charCodeAt(0);
                      return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
                    });
              }
            }
          }
      
          return parts.join('');
        }
      
        var rewritten = [];
        for (var i = 0, n = regexs.length; i < n; ++i) {
          var regex = regexs[i];
          if (regex.global || regex.multiline) { throw new Error('' + regex); }
          rewritten.push(
              '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
        }
      
        return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
      }
    
      /**
       * Split markup into a string of source code and an array mapping ranges in
       * that string to the text nodes in which they appear.
       *
       * <p>
       * The HTML DOM structure:</p>
       * <pre>
       * (Element   "p"
       *   (Element "b"
       *     (Text  "print "))       ; #1
       *   (Text    "'Hello '")      ; #2
       *   (Element "br")            ; #3
       *   (Text    "  + 'World';")) ; #4
       * </pre>
       * <p>
       * corresponds to the HTML
       * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>
       *
       * <p>
       * It will produce the output:</p>
       * <pre>
       * {
       *   sourceCode: "print 'Hello '\n  + 'World';",
       *   //                     1          2
       *   //           012345678901234 5678901234567
       *   spans: [0, #1, 6, #2, 14, #3, 15, #4]
       * }
       * </pre>
       * <p>
       * where #1 is a reference to the {@code "print "} text node above, and so
       * on for the other text nodes.
       * </p>
       *
       * <p>
       * The {@code} spans array is an array of pairs.  Even elements are the start
       * indices of substrings, and odd elements are the text nodes (or BR elements)
       * that contain the text for those substrings.
       * Substrings continue until the next index or the end of the source.
       * </p>
       *
       * @param {Node} node an HTML DOM subtree containing source-code.
       * @param {boolean} isPreformatted true if white-space in text nodes should
       *    be considered significant.
       * @return {Object} source code and the text nodes in which they occur.
       */
      function extractSourceSpans(node, isPreformatted) {
        var nocode = /(?:^|\s)nocode(?:\s|$)/;
      
        var chunks = [];
        var length = 0;
        var spans = [];
        var k = 0;
      
        function walk(node) {
          var type = node.nodeType;
          if (type == 1) {  // Element
            if (nocode.test(node.className)) { return; }
            for (var child = node.firstChild; child; child = child.nextSibling) {
              walk(child);
            }
            var nodeName = node.nodeName.toLowerCase();
            if ('br' === nodeName || 'li' === nodeName) {
              chunks[k] = '\n';
              spans[k << 1] = length++;
              spans[(k++ << 1) | 1] = node;
            }
          } else if (type == 3 || type == 4) {  // Text
            var text = node.nodeValue;
            if (text.length) {
              if (!isPreformatted) {
                text = text.replace(/[ \t\r\n]+/g, ' ');
              } else {
                text = text.replace(/\r\n?/g, '\n');  // Normalize newlines.
              }
              // TODO: handle tabs here?
              chunks[k] = text;
              spans[k << 1] = length;
              length += text.length;
              spans[(k++ << 1) | 1] = node;
            }
          }
        }
      
        walk(node);
      
        return {
          sourceCode: chunks.join('').replace(/\n$/, ''),
          spans: spans
        };
      }
    
      /**
       * Apply the given language handler to sourceCode and add the resulting
       * decorations to out.
       * @param {number} basePos the index of sourceCode within the chunk of source
       *    whose decorations are already present on out.
       */
      function appendDecorations(basePos, sourceCode, langHandler, out) {
        if (!sourceCode) { return; }
        var job = {
          sourceCode: sourceCode,
          basePos: basePos
        };
        langHandler(job);
        out.push.apply(out, job.decorations);
      }
    
      var notWs = /\S/;
    
      /**
       * Given an element, if it contains only one child element and any text nodes
       * it contains contain only space characters, return the sole child element.
       * Otherwise returns undefined.
       * <p>
       * This is meant to return the CODE element in {@code <pre><code ...>} when
       * there is a single child element that contains all the non-space textual
       * content, but not to return anything where there are multiple child elements
       * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
       * is textual content.
       */
      function childContentWrapper(element) {
        var wrapper = undefined;
        for (var c = element.firstChild; c; c = c.nextSibling) {
          var type = c.nodeType;
          wrapper = (type === 1)  // Element Node
              ? (wrapper ? element : c)
              : (type === 3)  // Text Node
              ? (notWs.test(c.nodeValue) ? element : wrapper)
              : wrapper;
        }
        return wrapper === element ? undefined : wrapper;
      }
    
      /** Given triples of [style, pattern, context] returns a lexing function,
        * The lexing function interprets the patterns to find token boundaries and
        * returns a decoration list of the form
        * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
        * where index_n is an index into the sourceCode, and style_n is a style
        * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
        * all characters in sourceCode[index_n-1:index_n].
        *
        * The stylePatterns is a list whose elements have the form
        * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
        *
        * Style is a style constant like PR_PLAIN, or can be a string of the
        * form 'lang-FOO', where FOO is a language extension describing the
        * language of the portion of the token in $1 after pattern executes.
        * E.g., if style is 'lang-lisp', and group 1 contains the text
        * '(hello (world))', then that portion of the token will be passed to the
        * registered lisp handler for formatting.
        * The text before and after group 1 will be restyled using this decorator
        * so decorators should take care that this doesn't result in infinite
        * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
        * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
        * '<script>foo()<\/script>', which would cause the current decorator to
        * be called with '<script>' which would not match the same rule since
        * group 1 must not be empty, so it would be instead styled as PR_TAG by
        * the generic tag rule.  The handler registered for the 'js' extension would
        * then be called with 'foo()', and finally, the current decorator would
        * be called with '<\/script>' which would not match the original rule and
        * so the generic tag rule would identify it as a tag.
        *
        * Pattern must only match prefixes, and if it matches a prefix, then that
        * match is considered a token with the same style.
        *
        * Context is applied to the last non-whitespace, non-comment token
        * recognized.
        *
        * Shortcut is an optional string of characters, any of which, if the first
        * character, gurantee that this pattern and only this pattern matches.
        *
        * @param {Array} shortcutStylePatterns patterns that always start with
        *   a known character.  Must have a shortcut string.
        * @param {Array} fallthroughStylePatterns patterns that will be tried in
        *   order if the shortcut ones fail.  May have shortcuts.
        *
        * @return {function (Object)} a
        *   function that takes source code and returns a list of decorations.
        */
      function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
        var shortcuts = {};
        var tokenizer;
        (function () {
          var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
          var allRegexs = [];
          var regexKeys = {};
          for (var i = 0, n = allPatterns.length; i < n; ++i) {
            var patternParts = allPatterns[i];
            var shortcutChars = patternParts[3];
            if (shortcutChars) {
              for (var c = shortcutChars.length; --c >= 0;) {
                shortcuts[shortcutChars.charAt(c)] = patternParts;
              }
            }
            var regex = patternParts[1];
            var k = '' + regex;
            if (!regexKeys.hasOwnProperty(k)) {
              allRegexs.push(regex);
              regexKeys[k] = null;
            }
          }
          allRegexs.push(/[\0-\uffff]/);
          tokenizer = combinePrefixPatterns(allRegexs);
        })();
    
        var nPatterns = fallthroughStylePatterns.length;
    
        /**
         * Lexes job.sourceCode and produces an output array job.decorations of
         * style classes preceded by the position at which they start in
         * job.sourceCode in order.
         *
         * @param {Object} job an object like <pre>{
         *    sourceCode: {string} sourceText plain text,
         *    basePos: {int} position of job.sourceCode in the larger chunk of
         *        sourceCode.
         * }</pre>
         */
        var decorate = function (job) {
          var sourceCode = job.sourceCode, basePos = job.basePos;
          /** Even entries are positions in source in ascending order.  Odd enties
            * are style markers (e.g., PR_COMMENT) that run from that position until
            * the end.
            * @type {Array.<number|string>}
            */
          var decorations = [basePos, PR_PLAIN];
          var pos = 0;  // index into sourceCode
          var tokens = sourceCode.match(tokenizer) || [];
          var styleCache = {};
    
          for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
            var token = tokens[ti];
            var style = styleCache[token];
            var match = void 0;
    
            var isEmbedded;
            if (typeof style === 'string') {
              isEmbedded = false;
            } else {
              var patternParts = shortcuts[token.charAt(0)];
              if (patternParts) {
                match = token.match(patternParts[1]);
                style = patternParts[0];
              } else {
                for (var i = 0; i < nPatterns; ++i) {
                  patternParts = fallthroughStylePatterns[i];
                  match = token.match(patternParts[1]);
                  if (match) {
                    style = patternParts[0];
                    break;
                  }
                }
    
                if (!match) {  // make sure that we make progress
                  style = PR_PLAIN;
                }
              }
    
              isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
              if (isEmbedded && !(match && typeof match[1] === 'string')) {
                isEmbedded = false;
                style = PR_SOURCE;
              }
    
              if (!isEmbedded) { styleCache[token] = style; }
            }
    
            var tokenStart = pos;
            pos += token.length;
    
            if (!isEmbedded) {
              decorations.push(basePos + tokenStart, style);
            } else {  // Treat group 1 as an embedded block of source code.
              var embeddedSource = match[1];
              var embeddedSourceStart = token.indexOf(embeddedSource);
              var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
              if (match[2]) {
                // If embeddedSource can be blank, then it would match at the
                // beginning which would cause us to infinitely recurse on the
                // entire token, so we catch the right context in match[2].
                embeddedSourceEnd = token.length - match[2].length;
                embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
              }
              var lang = style.substring(5);
              // Decorate the left of the embedded source
              appendDecorations(
                  basePos + tokenStart,
                  token.substring(0, embeddedSourceStart),
                  decorate, decorations);
              // Decorate the embedded source
              appendDecorations(
                  basePos + tokenStart + embeddedSourceStart,
                  embeddedSource,
                  langHandlerForExtension(lang, embeddedSource),
                  decorations);
              // Decorate the right of the embedded section
              appendDecorations(
                  basePos + tokenStart + embeddedSourceEnd,
                  token.substring(embeddedSourceEnd),
                  decorate, decorations);
            }
          }
          job.decorations = decorations;
        };
        return decorate;
      }
    
      /** returns a function that produces a list of decorations from source text.
        *
        * This code treats ", ', and ` as string delimiters, and \ as a string
        * escape.  It does not recognize perl's qq() style strings.
        * It has no special handling for double delimiter escapes as in basic, or
        * the tripled delimiters used in python, but should work on those regardless
        * although in those cases a single string literal may be broken up into
        * multiple adjacent string literals.
        *
        * It recognizes C, C++, and shell style comments.
        *
        * @param {Object} options a set of optional parameters.
        * @return {function (Object)} a function that examines the source code
        *     in the input job and builds the decoration list.
        */
      function sourceDecorator(options) {
        var shortcutStylePatterns = [], fallthroughStylePatterns = [];
        if (options['tripleQuotedStrings']) {
          // '''multi-line-string''', 'single-line-string', and double-quoted
          shortcutStylePatterns.push(
              [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
               null, '\'"']);
        } else if (options['multiLineStrings']) {
          // 'multi-line-string', "multi-line-string"
          shortcutStylePatterns.push(
              [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
               null, '\'"`']);
        } else {
          // 'single-line-string', "single-line-string"
          shortcutStylePatterns.push(
              [PR_STRING,
               /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
               null, '"\'']);
        }
        if (options['verbatimStrings']) {
          // verbatim-string-literal production from the C# grammar.  See issue 93.
          fallthroughStylePatterns.push(
              [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
        }
        var hc = options['hashComments'];
        if (hc) {
          if (options['cStyleComments']) {
            if (hc > 1) {  // multiline hash comments
              shortcutStylePatterns.push(
                  [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
            } else {
              // Stop C preprocessor declarations at an unclosed open comment
              shortcutStylePatterns.push(
                  [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
                   null, '#']);
            }
            // #include <stdio.h>
            fallthroughStylePatterns.push(
                [PR_STRING,
                 /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
                 null]);
          } else {
            shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
          }
        }
        if (options['cStyleComments']) {
          fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
          fallthroughStylePatterns.push(
              [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
        }
        var regexLiterals = options['regexLiterals'];
        if (regexLiterals) {
          /**
           * @const
           */
          var regexExcls = regexLiterals > 1
            ? ''  // Multiline regex literals
            : '\n\r';
          /**
           * @const
           */
          var regexAny = regexExcls ? '.' : '[\\S\\s]';
          /**
           * @const
           */
          var REGEX_LITERAL = (
              // A regular expression literal starts with a slash that is
              // not followed by * or / so that it is not confused with
              // comments.
              '/(?=[^/*' + regexExcls + '])'
              // and then contains any number of raw characters,
              + '(?:[^/\\x5B\\x5C' + regexExcls + ']'
              // escape sequences (\x5C),
              +    '|\\x5C' + regexAny
              // or non-nesting character sets (\x5B\x5D);
              +    '|\\x5B(?:[^\\x5C\\x5D' + regexExcls + ']'
              +             '|\\x5C' + regexAny + ')*(?:\\x5D|$))+'
              // finally closed by a /.
              + '/');
          fallthroughStylePatterns.push(
              ['lang-regex',
               RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
               ]);
        }
    
        var types = options['types'];
        if (types) {
          fallthroughStylePatterns.push([PR_TYPE, types]);
        }
    
        var keywords = ("" + options['keywords']).replace(/^ | $/g, '');
        if (keywords.length) {
          fallthroughStylePatterns.push(
              [PR_KEYWORD,
               new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
               null]);
        }
    
        shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    
        var punctuation =
          // The Bash man page says
    
          // A word is a sequence of characters considered as a single
          // unit by GRUB. Words are separated by metacharacters,
          // which are the following plus space, tab, and newline: { }
          // | & $ ; < >
          // ...
          
          // A word beginning with # causes that word and all remaining
          // characters on that line to be ignored.
    
          // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
          // comment but empirically
          // $ echo {#}
          // {#}
          // $ echo \$#
          // $#
          // $ echo }#
          // }#
    
          // so /(?:^|[|&;<>\s])/ is more appropriate.
    
          // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
          // suggests that this definition is compatible with a
          // default mode that tries to use a single token definition
          // to recognize both bash/python style comments and C
          // preprocessor directives.
    
          // This definition of punctuation does not include # in the list of
          // follow-on exclusions, so # will not be broken before if preceeded
          // by a punctuation character.  We could try to exclude # after
          // [|&;<>] but that doesn't seem to cause many major problems.
          // If that does turn out to be a problem, we should change the below
          // when hc is truthy to include # in the run of punctuation characters
          // only when not followint [|&;<>].
          '^.[^\\s\\w.$@\'"`/\\\\]*';
        if (options['regexLiterals']) {
          punctuation += '(?!\s*\/)';
        }
    
        fallthroughStylePatterns.push(
            // TODO(mikesamuel): recognize non-latin letters and numerals in idents
            [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
            [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
            [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
            [PR_LITERAL,
             new RegExp(
                 '^(?:'
                 // A hex number
                 + '0x[a-f0-9]+'
                 // or an octal or decimal number,
                 + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
                 // possibly in scientific notation
                 + '(?:e[+\\-]?\\d+)?'
                 + ')'
                 // with an optional modifier like UL for unsigned long
                 + '[a-z]*', 'i'),
             null, '0123456789'],
            // Don't treat escaped quotes in bash as starting strings.
            // See issue 144.
            [PR_PLAIN,       /^\\[\s\S]?/, null],
            [PR_PUNCTUATION, new RegExp(punctuation), null]);
    
        return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
      }
    
      var decorateSource = sourceDecorator({
            'keywords': ALL_KEYWORDS,
            'hashComments': true,
            'cStyleComments': true,
            'multiLineStrings': true,
            'regexLiterals': true
          });
    
      /**
       * Given a DOM subtree, wraps it in a list, and puts each line into its own
       * list item.
       *
       * @param {Node} node modified in place.  Its content is pulled into an
       *     HTMLOListElement, and each line is moved into a separate list item.
       *     This requires cloning elements, so the input might not have unique
       *     IDs after numbering.
       * @param {boolean} isPreformatted true iff white-space in text nodes should
       *     be treated as significant.
       */
      function numberLines(node, opt_startLineNum, isPreformatted) {
        var nocode = /(?:^|\s)nocode(?:\s|$)/;
        var lineBreak = /\r\n?|\n/;
      
        var document = node.ownerDocument;
      
        var li = document.createElement('li');
        while (node.firstChild) {
          li.appendChild(node.firstChild);
        }
        // An array of lines.  We split below, so this is initialized to one
        // un-split line.
        var listItems = [li];
      
        function walk(node) {
          var type = node.nodeType;
          if (type == 1 && !nocode.test(node.className)) {  // Element
            if ('br' === node.nodeName) {
              breakAfter(node);
              // Discard the <BR> since it is now flush against a </LI>.
              if (node.parentNode) {
                node.parentNode.removeChild(node);
              }
            } else {
              for (var child = node.firstChild; child; child = child.nextSibling) {
                walk(child);
              }
            }
          } else if ((type == 3 || type == 4) && isPreformatted) {  // Text
            var text = node.nodeValue;
            var match = text.match(lineBreak);
            if (match) {
              var firstLine = text.substring(0, match.index);
              node.nodeValue = firstLine;
              var tail = text.substring(match.index + match[0].length);
              if (tail) {
                var parent = node.parentNode;
                parent.insertBefore(
                  document.createTextNode(tail), node.nextSibling);
              }
              breakAfter(node);
              if (!firstLine) {
                // Don't leave blank text nodes in the DOM.
                node.parentNode.removeChild(node);
              }
            }
          }
        }
      
        // Split a line after the given node.
        function breakAfter(lineEndNode) {
          // If there's nothing to the right, then we can skip ending the line
          // here, and move root-wards since splitting just before an end-tag
          // would require us to create a bunch of empty copies.
          while (!lineEndNode.nextSibling) {
            lineEndNode = lineEndNode.parentNode;
            if (!lineEndNode) { return; }
          }
      
          function breakLeftOf(limit, copy) {
            // Clone shallowly if this node needs to be on both sides of the break.
            var rightSide = copy ? limit.cloneNode(false) : limit;
            var parent = limit.parentNode;
            if (parent) {
              // We clone the parent chain.
              // This helps us resurrect important styling elements that cross lines.
              // E.g. in <i>Foo<br>Bar</i>
              // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
              var parentClone = breakLeftOf(parent, 1);
              // Move the clone and everything to the right of the original
              // onto the cloned parent.
              var next = limit.nextSibling;
              parentClone.appendChild(rightSide);
              for (var sibling = next; sibling; sibling = next) {
                next = sibling.nextSibling;
                parentClone.appendChild(sibling);
              }
            }
            return rightSide;
          }
      
          var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
      
          // Walk the parent chain until we reach an unattached LI.
          for (var parent;
               // Check nodeType since IE invents document fragments.
               (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
            copiedListItem = parent;
          }
          // Put it on the list of lines for later processing.
          listItems.push(copiedListItem);
        }
      
        // Split lines while there are lines left to split.
        for (var i = 0;  // Number of lines that have been split so far.
             i < listItems.length;  // length updated by breakAfter calls.
             ++i) {
          walk(listItems[i]);
        }
      
        // Make sure numeric indices show correctly.
        if (opt_startLineNum === (opt_startLineNum|0)) {
          listItems[0].setAttribute('value', opt_startLineNum);
        }
      
        var ol = document.createElement('ol');
        ol.className = 'linenums';
        var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
        for (var i = 0, n = listItems.length; i < n; ++i) {
          li = listItems[i];
          // Stick a class on the LIs so that stylesheets can
          // color odd/even rows, or any other row pattern that
          // is co-prime with 10.
          li.className = 'L' + ((i + offset) % 10);
          if (!li.firstChild) {
            li.appendChild(document.createTextNode('\xA0'));
          }
          ol.appendChild(li);
        }
      
        node.appendChild(ol);
      }
      /**
       * Breaks {@code job.sourceCode} around style boundaries in
       * {@code job.decorations} and modifies {@code job.sourceNode} in place.
       * @param {Object} job like <pre>{
       *    sourceCode: {string} source as plain text,
       *    sourceNode: {HTMLElement} the element containing the source,
       *    spans: {Array.<number|Node>} alternating span start indices into source
       *       and the text node or element (e.g. {@code <BR>}) corresponding to that
       *       span.
       *    decorations: {Array.<number|string} an array of style classes preceded
       *       by the position at which they start in job.sourceCode in order
       * }</pre>
       * @private
       */
      function recombineTagsAndDecorations(job) {
        var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent);
        isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;
        var newlineRe = /\n/g;
      
        var source = job.sourceCode;
        var sourceLength = source.length;
        // Index into source after the last code-unit recombined.
        var sourceIndex = 0;
      
        var spans = job.spans;
        var nSpans = spans.length;
        // Index into spans after the last span which ends at or before sourceIndex.
        var spanIndex = 0;
      
        var decorations = job.decorations;
        var nDecorations = decorations.length;
        // Index into decorations after the last decoration which ends at or before
        // sourceIndex.
        var decorationIndex = 0;
      
        // Remove all zero-length decorations.
        decorations[nDecorations] = sourceLength;
        var decPos, i;
        for (i = decPos = 0; i < nDecorations;) {
          if (decorations[i] !== decorations[i + 2]) {
            decorations[decPos++] = decorations[i++];
            decorations[decPos++] = decorations[i++];
          } else {
            i += 2;
          }
        }
        nDecorations = decPos;
      
        // Simplify decorations.
        for (i = decPos = 0; i < nDecorations;) {
          var startPos = decorations[i];
          // Conflate all adjacent decorations that use the same style.
          var startDec = decorations[i + 1];
          var end = i + 2;
          while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {
            end += 2;
          }
          decorations[decPos++] = startPos;
          decorations[decPos++] = startDec;
          i = end;
        }
      
        nDecorations = decorations.length = decPos;
      
        var sourceNode = job.sourceNode;
        var oldDisplay;
        if (sourceNode) {
          oldDisplay = sourceNode.style.display;
          sourceNode.style.display = 'none';
        }
        try {
          var decoration = null;
          while (spanIndex < nSpans) {
            var spanStart = spans[spanIndex];
            var spanEnd = spans[spanIndex + 2] || sourceLength;
      
            var decEnd = decorations[decorationIndex + 2] || sourceLength;
      
            var end = Math.min(spanEnd, decEnd);
      
            var textNode = spans[spanIndex + 1];
            var styledText;
            if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s
                // Don't introduce spans around empty text nodes.
                && (styledText = source.substring(sourceIndex, end))) {
              // This may seem bizarre, and it is.  Emitting LF on IE causes the
              // code to display with spaces instead of line breaks.
              // Emitting Windows standard issue linebreaks (CRLF) causes a blank
              // space to appear at the beginning of every line but the first.
              // Emitting an old Mac OS 9 line separator makes everything spiffy.
              if (isIE8OrEarlier) {
                styledText = styledText.replace(newlineRe, '\r');
              }
              textNode.nodeValue = styledText;
              var document = textNode.ownerDocument;
              var span = document.createElement('span');
              span.className = decorations[decorationIndex + 1];
              var parentNode = textNode.parentNode;
              parentNode.replaceChild(span, textNode);
              span.appendChild(textNode);
              if (sourceIndex < spanEnd) {  // Split off a text node.
                spans[spanIndex + 1] = textNode
                    // TODO: Possibly optimize by using '' if there's no flicker.
                    = document.createTextNode(source.substring(end, spanEnd));
                parentNode.insertBefore(textNode, span.nextSibling);
              }
            }
      
            sourceIndex = end;
      
            if (sourceIndex >= spanEnd) {
              spanIndex += 2;
            }
            if (sourceIndex >= decEnd) {
              decorationIndex += 2;
            }
          }
        } finally {
          if (sourceNode) {
            sourceNode.style.display = oldDisplay;
          }
        }
      }
    
      /** Maps language-specific file extensions to handlers. */
      var langHandlerRegistry = {};
      /** Register a language handler for the given file extensions.
        * @param {function (Object)} handler a function from source code to a list
        *      of decorations.  Takes a single argument job which describes the
        *      state of the computation.   The single parameter has the form
        *      {@code {
        *        sourceCode: {string} as plain text.
        *        decorations: {Array.<number|string>} an array of style classes
        *                     preceded by the position at which they start in
        *                     job.sourceCode in order.
        *                     The language handler should assigned this field.
        *        basePos: {int} the position of source in the larger source chunk.
        *                 All positions in the output decorations array are relative
        *                 to the larger source chunk.
        *      } }
        * @param {Array.<string>} fileExtensions
        */
      function registerLangHandler(handler, fileExtensions) {
        for (var i = fileExtensions.length; --i >= 0;) {
          var ext = fileExtensions[i];
          if (!langHandlerRegistry.hasOwnProperty(ext)) {
            langHandlerRegistry[ext] = handler;
          } else if (win['console']) {
            console['warn']('cannot override language handler %s', ext);
          }
        }
      }
      function langHandlerForExtension(extension, source) {
        if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
          // Treat it as markup if the first non whitespace character is a < and
          // the last non-whitespace character is a >.
          extension = /^\s*</.test(source)
              ? 'default-markup'
              : 'default-code';
        }
        return langHandlerRegistry[extension];
      }
      registerLangHandler(decorateSource, ['default-code']);
      registerLangHandler(
          createSimpleLexer(
              [],
              [
               [PR_PLAIN,       /^[^<?]+/],
               [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
               [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
               // Unescaped content in an unknown language
               ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
               ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
               [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
               ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
               // Unescaped content in javascript.  (Or possibly vbscript).
               ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
               // Contains unescaped stylesheet content
               ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
               ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
              ]),
          ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
      registerLangHandler(
          createSimpleLexer(
              [
               [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
               [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
               ],
              [
               [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
               [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
               ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
               [PR_PUNCTUATION,  /^[=<>\/]+/],
               ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
               ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
               ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
               ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
               ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
               ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
               ]),
          ['in.tag']);
      registerLangHandler(
          createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
      registerLangHandler(sourceDecorator({
              'keywords': CPP_KEYWORDS,
              'hashComments': true,
              'cStyleComments': true,
              'types': C_TYPES
            }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
      registerLangHandler(sourceDecorator({
              'keywords': 'null,true,false'
            }), ['json']);
      registerLangHandler(sourceDecorator({
              'keywords': CSHARP_KEYWORDS,
              'hashComments': true,
              'cStyleComments': true,
              'verbatimStrings': true,
              'types': C_TYPES
            }), ['cs']);
      registerLangHandler(sourceDecorator({
              'keywords': JAVA_KEYWORDS,
              'cStyleComments': true
            }), ['java']);
      registerLangHandler(sourceDecorator({
              'keywords': SH_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true
            }), ['bash', 'bsh', 'csh', 'sh']);
      registerLangHandler(sourceDecorator({
              'keywords': PYTHON_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'tripleQuotedStrings': true
            }), ['cv', 'py', 'python']);
      registerLangHandler(sourceDecorator({
              'keywords': PERL_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'regexLiterals': 2  // multiline regex literals
            }), ['perl', 'pl', 'pm']);
      registerLangHandler(sourceDecorator({
              'keywords': RUBY_KEYWORDS,
              'hashComments': true,
              'multiLineStrings': true,
              'regexLiterals': true
            }), ['rb', 'ruby']);
      registerLangHandler(sourceDecorator({
              'keywords': JSCRIPT_KEYWORDS,
              'cStyleComments': true,
              'regexLiterals': true
            }), ['javascript', 'js']);
      registerLangHandler(sourceDecorator({
              'keywords': COFFEE_KEYWORDS,
              'hashComments': 3,  // ### style block comments
              'cStyleComments': true,
              'multilineStrings': true,
              'tripleQuotedStrings': true,
              'regexLiterals': true
            }), ['coffee']);
      registerLangHandler(sourceDecorator({
              'keywords': RUST_KEYWORDS,
              'cStyleComments': true,
              'multilineStrings': true
            }), ['rc', 'rs', 'rust']);
      registerLangHandler(
          createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    
      function applyDecorator(job) {
        var opt_langExtension = job.langExtension;
    
        try {
          // Extract tags, and convert the source code to plain text.
          var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);
          /** Plain text. @type {string} */
          var source = sourceAndSpans.sourceCode;
          job.sourceCode = source;
          job.spans = sourceAndSpans.spans;
          job.basePos = 0;
    
          // Apply the appropriate language handler
          langHandlerForExtension(opt_langExtension, source)(job);
    
          // Integrate the decorations and tags back into the source code,
          // modifying the sourceNode in place.
          recombineTagsAndDecorations(job);
        } catch (e) {
          if (win['console']) {
            console['log'](e && e['stack'] || e);
          }
        }
      }
    
      /**
       * Pretty print a chunk of code.
       * @param sourceCodeHtml {string} The HTML to pretty print.
       * @param opt_langExtension {string} The language name to use.
       *     Typically, a filename extension like 'cpp' or 'java'.
       * @param opt_numberLines {number|boolean} True to number lines,
       *     or the 1-indexed number of the first line in sourceCodeHtml.
       */
      function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
        var container = document.createElement('div');
        // This could cause images to load and onload listeners to fire.
        // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
        // We assume that the inner HTML is from a trusted source.
        // The pre-tag is required for IE8 which strips newlines from innerHTML
        // when it is injected into a <pre> tag.
        // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
        // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
        container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';
        container = container.firstChild;
        if (opt_numberLines) {
          numberLines(container, opt_numberLines, true);
        }
    
        var job = {
          langExtension: opt_langExtension,
          numberLines: opt_numberLines,
          sourceNode: container,
          pre: 1
        };
        applyDecorator(job);
        return container.innerHTML;
      }
    
       /**
        * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
        * {@code class=prettyprint} and prettify them.
        *
        * @param {Function} opt_whenDone called when prettifying is done.
        * @param {HTMLElement|HTMLDocument} opt_root an element or document
        *   containing all the elements to pretty print.
        *   Defaults to {@code document.body}.
        */
      function $prettyPrint(opt_whenDone, opt_root) {
        var root = opt_root || document.body;
        var doc = root.ownerDocument || document;
        function byTagName(tn) { return root.getElementsByTagName(tn); }
        // fetch a list of nodes to rewrite
        var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
        var elements = [];
        for (var i = 0; i < codeSegments.length; ++i) {
          for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
            elements.push(codeSegments[i][j]);
          }
        }
        codeSegments = null;
    
        var clock = Date;
        if (!clock['now']) {
          clock = { 'now': function () { return +(new Date); } };
        }
    
        // The loop is broken into a series of continuations to make sure that we
        // don't make the browser unresponsive when rewriting a large page.
        var k = 0;
        var prettyPrintingJob;
    
        var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
        var prettyPrintRe = /\bprettyprint\b/;
        var prettyPrintedRe = /\bprettyprinted\b/;
        var preformattedTagNameRe = /pre|xmp/i;
        var codeRe = /^code$/i;
        var preCodeXmpRe = /^(?:pre|code|xmp)$/i;
        var EMPTY = {};
    
        function doWork() {
          var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?
                         clock['now']() + 250 /* ms */ :
                         Infinity);
          for (; k < elements.length && clock['now']() < endTime; k++) {
            var cs = elements[k];
    
            // Look for a preceding comment like
            // <?prettify lang="..." linenums="..."?>
            var attrs = EMPTY;
            {
              for (var preceder = cs; (preceder = preceder.previousSibling);) {
                var nt = preceder.nodeType;
                // <?foo?> is parsed by HTML 5 to a comment node (8)
                // like <!--?foo?-->, but in XML is a processing instruction
                var value = (nt === 7 || nt === 8) && preceder.nodeValue;
                if (value
                    ? !/^\??prettify\b/.test(value)
                    : (nt !== 3 || /\S/.test(preceder.nodeValue))) {
                  // Skip over white-space text nodes but not others.
                  break;
                }
                if (value) {
                  attrs = {};
                  value.replace(
                      /\b(\w+)=([\w:.%+-]+)/g,
                    function (_, name, value) { attrs[name] = value; });
                  break;
                }
              }
            }
    
            var className = cs.className;
            if ((attrs !== EMPTY || prettyPrintRe.test(className))
                // Don't redo this if we've already done it.
                // This allows recalling pretty print to just prettyprint elements
                // that have been added to the page since last call.
                && !prettyPrintedRe.test(className)) {
    
              // make sure this is not nested in an already prettified element
              var nested = false;
              for (var p = cs.parentNode; p; p = p.parentNode) {
                var tn = p.tagName;
                if (preCodeXmpRe.test(tn)
                    && p.className && prettyPrintRe.test(p.className)) {
                  nested = true;
                  break;
                }
              }
              if (!nested) {
                // Mark done.  If we fail to prettyprint for whatever reason,
                // we shouldn't try again.
                cs.className += ' prettyprinted';
    
                // If the classes includes a language extensions, use it.
                // Language extensions can be specified like
                //     <pre class="prettyprint lang-cpp">
                // the language extension "cpp" is used to find a language handler
                // as passed to PR.registerLangHandler.
                // HTML5 recommends that a language be specified using "language-"
                // as the prefix instead.  Google Code Prettify supports both.
                // http://dev.w3.org/html5/spec-author-view/the-code-element.html
                var langExtension = attrs['lang'];
                if (!langExtension) {
                  langExtension = className.match(langExtensionRe);
                  // Support <pre class="prettyprint"><code class="language-c">
                  var wrapper;
                  if (!langExtension && (wrapper = childContentWrapper(cs))
                      && codeRe.test(wrapper.tagName)) {
                    langExtension = wrapper.className.match(langExtensionRe);
                  }
    
                  if (langExtension) { langExtension = langExtension[1]; }
                }
    
                var preformatted;
                if (preformattedTagNameRe.test(cs.tagName)) {
                  preformatted = 1;
                } else {
                  var currentStyle = cs['currentStyle'];
                  var defaultView = doc.defaultView;
                  var whitespace = (
                      currentStyle
                      ? currentStyle['whiteSpace']
                      : (defaultView
                         && defaultView.getComputedStyle)
                      ? defaultView.getComputedStyle(cs, null)
                      .getPropertyValue('white-space')
                      : 0);
                  preformatted = whitespace
                      && 'pre' === whitespace.substring(0, 3);
                }
    
                // Look for a class like linenums or linenums:<n> where <n> is the
                // 1-indexed number of the first line.
                var lineNums = attrs['linenums'];
                if (!(lineNums = lineNums === 'true' || +lineNums)) {
                  lineNums = className.match(/\blinenums\b(?::(\d+))?/);
                  lineNums =
                    lineNums
                    ? lineNums[1] && lineNums[1].length
                      ? +lineNums[1] : true
                    : false;
                }
                if (lineNums) { numberLines(cs, lineNums, preformatted); }
    
                // do the pretty printing
                prettyPrintingJob = {
                  langExtension: langExtension,
                  sourceNode: cs,
                  numberLines: lineNums,
                  pre: preformatted
                };
                applyDecorator(prettyPrintingJob);
              }
            }
          }
          if (k < elements.length) {
            // finish up in a continuation
            setTimeout(doWork, 250);
          } else if ('function' === typeof opt_whenDone) {
            opt_whenDone();
          }
        }
    
        doWork();
      }
    
      /**
       * Contains functions for creating and registering new language handlers.
       * @type {Object}
       */
      var PR = win['PR'] = {
            'createSimpleLexer': createSimpleLexer,
            'registerLangHandler': registerLangHandler,
            'sourceDecorator': sourceDecorator,
            'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
            'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
            'PR_COMMENT': PR_COMMENT,
            'PR_DECLARATION': PR_DECLARATION,
            'PR_KEYWORD': PR_KEYWORD,
            'PR_LITERAL': PR_LITERAL,
            'PR_NOCODE': PR_NOCODE,
            'PR_PLAIN': PR_PLAIN,
            'PR_PUNCTUATION': PR_PUNCTUATION,
            'PR_SOURCE': PR_SOURCE,
            'PR_STRING': PR_STRING,
            'PR_TAG': PR_TAG,
            'PR_TYPE': PR_TYPE,
            'prettyPrintOne':
               IN_GLOBAL_SCOPE
                 ? (win['prettyPrintOne'] = $prettyPrintOne)
                 : (prettyPrintOne = $prettyPrintOne),
            'prettyPrint': prettyPrint =
               IN_GLOBAL_SCOPE
                 ? (win['prettyPrint'] = $prettyPrint)
                 : (prettyPrint = $prettyPrint)
          };
    
      // Make PR available via the Asynchronous Module Definition (AMD) API.
      // Per https://github.com/amdjs/amdjs-api/wiki/AMD:
      // The Asynchronous Module Definition (AMD) API specifies a
      // mechanism for defining modules such that the module and its
      // dependencies can be asynchronously loaded.
      // ...
      // To allow a clear indicator that a global define function (as
      // needed for script src browser loading) conforms to the AMD API,
      // any global define function SHOULD have a property called "amd"
      // whose value is an object. This helps avoid conflict with any
      // other existing JavaScript code that could have defined a define()
      // function that does not conform to the AMD API.
      if (typeof define === "function" && define['amd']) {
        define("google-code-prettify", [], function () {
          return PR; 
        });
      }
    })();
    
    var Markdown;
    
    if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module
        Markdown = exports;
    else
        Markdown = {};
    
    // The following text is included for historical reasons, but should
    // be taken with a pinch of salt; it's not all true anymore.
    
    //
    // Wherever possible, Showdown is a straight, line-by-line port
    // of the Perl version of Markdown.
    //
    // This is not a normal parser design; it's basically just a
    // series of string substitutions.  It's hard to read and
    // maintain this way,  but keeping Showdown close to the original
    // design makes it easier to port new features.
    //
    // More importantly, Showdown behaves like markdown.pl in most
    // edge cases.  So web applications can do client-side preview
    // in Javascript, and then build identical HTML on the server.
    //
    // This port needs the new RegExp functionality of ECMA 262,
    // 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
    // should do fine.  Even with the new regular expression features,
    // We do a lot of work to emulate Perl's regex functionality.
    // The tricky changes in this file mostly have the "attacklab:"
    // label.  Major or self-explanatory changes don't.
    //
    // Smart diff tools like Araxis Merge will be able to match up
    // this file with markdown.pl in a useful way.  A little tweaking
    // helps: in a copy of markdown.pl, replace "#" with "//" and
    // replace "$text" with "text".  Be sure to ignore whitespace
    // and line endings.
    //
    
    
    //
    // Usage:
    //
    //   var text = "Markdown *rocks*.";
    //
    //   var converter = new Markdown.Converter();
    //   var html = converter.makeHtml(text);
    //
    //   alert(html);
    //
    // Note: move the sample code to the bottom of this
    // file before uncommenting it.
    //
    
    (function () {
    
        function identity(x) { return x; }
        function returnFalse(x) { return false; }
    
        function HookCollection() { }
    
        HookCollection.prototype = {
    
            chain: function (hookname, func) {
                var original = this[hookname];
                if (!original)
                    throw new Error("unknown hook " + hookname);
    
                if (original === identity)
                    this[hookname] = func;
                else
                    this[hookname] = function (text) {
                        var args = Array.prototype.slice.call(arguments, 0);
                        args[0] = original.apply(null, args);
                        return func.apply(null, args);
                    };
            },
            set: function (hookname, func) {
                if (!this[hookname])
                    throw new Error("unknown hook " + hookname);
                this[hookname] = func;
            },
            addNoop: function (hookname) {
                this[hookname] = identity;
            },
            addFalse: function (hookname) {
                this[hookname] = returnFalse;
            }
        };
    
        Markdown.HookCollection = HookCollection;
    
        // g_urls and g_titles allow arbitrary user-entered strings as keys. This
        // caused an exception (and hence stopped the rendering) when the user entered
        // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
        // (since no builtin property starts with "s_"). See
        // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
        // (granted, switching from Array() to Object() alone would have left only __proto__
        // to be a problem)
        function SaveHash() { }
        SaveHash.prototype = {
            set: function (key, value) {
                this["s_" + key] = value;
            },
            get: function (key) {
                return this["s_" + key];
            }
        };
    
        Markdown.Converter = function () {
            var options = {};
            this.setOptions = function(optionsParam) {
                options = optionsParam;
            };
    
            var pluginHooks = this.hooks = new HookCollection();
    
            // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
            pluginHooks.addNoop("plainLinkText");
    
            // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
            pluginHooks.addNoop("preConversion");
    
            // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have
            pluginHooks.addNoop("postNormalization");
    
            // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively
            // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner
            // call will receive outdented text.
            pluginHooks.addNoop("preBlockGamut");
            pluginHooks.addNoop("postBlockGamut");
    
            // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made
            pluginHooks.addNoop("preSpanGamut");
            pluginHooks.addNoop("postSpanGamut");
    
            // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
            pluginHooks.addNoop("postConversion");
    
            //
            // Private state of the converter instance:
            //
    
            // Global hashes, used by various utility routines
            var g_urls;
            var g_titles;
            var g_html_blocks;
    
            // Used to track when we're inside an ordered or unordered list
            // (see _ProcessListItems() for details):
            var g_list_level;
    
            this.makeHtml = function (text) {
    
                //
                // Main function. The order in which other subs are called here is
                // essential. Link and image substitutions need to happen before
                // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
                // and <img> tags get encoded.
                //
    
                // This will only happen if makeHtml on the same converter instance is called from a plugin hook.
                // Don't do that.
                if (g_urls)
                    throw new Error("Recursive call to converter.makeHtml");
    
                // Create the private state objects.
                g_urls = new SaveHash();
                g_titles = new SaveHash();
                g_html_blocks = [];
                g_list_level = 0;
    
                text = pluginHooks.preConversion(text);
    
                // attacklab: Replace ~ with ~T
                // This lets us use tilde as an escape char to avoid md5 hashes
                // The choice of character is arbitray; anything that isn't
                // magic in Markdown will work.
                text = text.replace(/~/g, "~T");
    
                // attacklab: Replace $ with ~D
                // RegExp interprets $ as a special character
                // when it's in a replacement string
                text = text.replace(/\$/g, "~D");
    
                // Standardize line endings
                text = text.replace(/\r\n/g, "\n"); // DOS to Unix
                text = text.replace(/\r/g, "\n"); // Mac to Unix
    
                // Make sure text begins and ends with a couple of newlines:
                text = "\n\n" + text + "\n\n";
    
                // Convert all tabs to spaces.
                text = _Detab(text);
    
                // Strip any lines consisting only of spaces and tabs.
                // This makes subsequent regexen easier to write, because we can
                // match consecutive blank lines with /\n+/ instead of something
                // contorted like /[ \t]*\n+/ .
                text = text.replace(/^[ \t]+$/mg, "");
    
                text = pluginHooks.postNormalization(text);
    
                // Turn block-level HTML blocks into hash entries
                text = _HashHTMLBlocks(text);
    
                // Strip link definitions, store in hashes.
                text = _StripLinkDefinitions(text);
    
                text = _RunBlockGamut(text);
    
                text = _UnescapeSpecialChars(text);
    
                // attacklab: Restore dollar signs
                text = text.replace(/~D/g, "$$");
    
                // attacklab: Restore tildes
                text = text.replace(/~T/g, "~");
    
                text = pluginHooks.postConversion(text);
    
                g_html_blocks = g_titles = g_urls = null;
    
                return text;
            };
    
            function _StripLinkDefinitions(text) {
                //
                // Strips link definitions from text, stores the URLs and titles in
                // hash references.
                //
    
                // Link defs are in the form: ^[id]: url "optional title"
    
                /*
                text = text.replace(/
                    ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
                    [ \t]*
                    \n?                 // maybe *one* newline
                    [ \t]*
                    <?(\S+?)>?          // url = $2
                    (?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below
                    [ \t]*
                    \n?                 // maybe one newline
                    [ \t]*
                    (                   // (potential) title = $3
                        (\n*)           // any lines skipped = $4 attacklab: lookbehind removed
                        [ \t]+
                        ["(]
                        (.+?)           // title = $5
                        [")]
                        [ \t]*
                    )?                  // title is optional
                    (?:\n+|$)
                /gm, function(){...});
                */
    
                text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
                    function (wholeMatch, m1, m2, m3, m4, m5) {
                        m1 = m1.toLowerCase();
                        g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive
                        if (m4) {
                            // Oops, found blank lines, so it's not a title.
                            // Put back the parenthetical statement we stole.
                            return m3;
                        } else if (m5) {
                            g_titles.set(m1, m5.replace(/"/g, "&quot;"));
                        }
    
                        // Completely remove the definition from the text
                        return "";
                    }
                );
    
                return text;
            }
    
            function _HashHTMLBlocks(text) {
    
                // Hashify HTML blocks:
                // We only want to do this for block-level HTML tags, such as headers,
                // lists, and tables. That's because we still want to wrap <p>s around
                // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
                // phrase emphasis, and spans. The list of tags we're looking for is
                // hard-coded:
                var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
                var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
    
                // First, look for nested blocks, e.g.:
                //   <div>
                //     <div>
                //     tags for inner block must be indented.
                //     </div>
                //   </div>
                //
                // The outermost tags must start at the left margin for this to match, and
                // the inner nested divs must be indented.
                // We need to do this before the next, more liberal match, because the next
                // match will start at the first `<div>` and stop at the first `</div>`.
    
                // attacklab: This regex can be expensive when it fails.
    
                /*
                text = text.replace(/
                    (                       // save in $1
                        ^                   // start of line  (with /m)
                        <($block_tags_a)    // start tag = $2
                        \b                  // word break
                                            // attacklab: hack around khtml/pcre bug...
                        [^\r]*?\n           // any number of lines, minimally matching
                        </\2>               // the matching end tag
                        [ \t]*              // trailing spaces/tabs
                        (?=\n+)             // followed by a newline
                    )                       // attacklab: there are sentinel newlines at end of document
                /gm,function(){...}};
                */
                text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
    
                //
                // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
                //
    
                /*
                text = text.replace(/
                    (                       // save in $1
                        ^                   // start of line  (with /m)
                        <($block_tags_b)    // start tag = $2
                        \b                  // word break
                                            // attacklab: hack around khtml/pcre bug...
                        [^\r]*?             // any number of lines, minimally matching
                        .*</\2>             // the matching end tag
                        [ \t]*              // trailing spaces/tabs
                        (?=\n+)             // followed by a newline
                    )                       // attacklab: there are sentinel newlines at end of document
                /gm,function(){...}};
                */
                //text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
    
                // The .* is highly CPU consuming and I don't know what it's for (not even in the original showdown lib)
                text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
    
                // Special case just for <hr />. It was easier to make a special case than
                // to make the other regex more complicated.
    
                /*
                text = text.replace(/
                    \n                  // Starting after a blank line
                    [ ]{0,3}
                    (                   // save in $1
                        (<(hr)          // start tag = $2
                            \b          // word break
                            ([^<>])*?
                        \/?>)           // the matching end tag
                        [ \t]*
                        (?=\n{2,})      // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
    
                // Special case for standalone HTML comments:
    
                /*
                text = text.replace(/
                    \n\n                                            // Starting after a blank line
                    [ ]{0,3}                                        // attacklab: g_tab_width - 1
                    (                                               // save in $1
                        <!
                        (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
                        >
                        [ \t]*
                        (?=\n{2,})                                  // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);
    
                // PHP and ASP-style processor instructions (<?...?> and <%...%>)
    
                /*
                text = text.replace(/
                    (?:
                        \n\n            // Starting after a blank line
                    )
                    (                   // save in $1
                        [ ]{0,3}        // attacklab: g_tab_width - 1
                        (?:
                            <([?%])     // $2
                            [^\r]*?
                            \2>
                        )
                        [ \t]*
                        (?=\n{2,})      // followed by a blank line
                    )
                /g,hashElement);
                */
                text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);
    
                return text;
            }
    
            function hashElement(wholeMatch, m1) {
                var blockText = m1;
    
                // Undo double lines
                blockText = blockText.replace(/^\n+/, "");
    
                // strip trailing blank lines
                blockText = blockText.replace(/\n+$/g, "");
    
                // Replace the element text with a marker ("~KxK" where x is its key)
                blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";
    
                return blockText;
            }
    
            var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); }
    
            function _RunBlockGamut(text, doNotUnhash) {
                //
                // These are all the transformations that form block-level
                // tags like paragraphs, headers, and list items.
                //
    
                text = pluginHooks.preBlockGamut(text, blockGamutHookCallback);
    
                text = _DoHeaders(text);
    
                // Do Horizontal Rules:
                var replacement = "<hr />\n";
                text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
                text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
                text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);
    
                text = _DoLists(text);
                text = _DoCodeBlocks(text);
                text = _DoBlockQuotes(text);
    
                text = pluginHooks.postBlockGamut(text, blockGamutHookCallback);
    
                // We already ran _HashHTMLBlocks() before, in Markdown(), but that
                // was to escape raw HTML in the original Markdown source. This time,
                // we're escaping the markup we've just created, so that we don't wrap
                // <p> tags around block-level tags.
                text = _HashHTMLBlocks(text);
                text = _FormParagraphs(text, doNotUnhash);
    
                return text;
            }
    
            function _RunSpanGamut(text) {
                //
                // These are all the transformations that occur *within* block-level
                // tags like paragraphs, headers, and list items.
                //
    
                text = pluginHooks.preSpanGamut(text);
    
                text = _DoCodeSpans(text);
                text = _EscapeSpecialCharsWithinTagAttributes(text);
                text = _EncodeBackslashEscapes(text);
    
                // Process anchor and image tags. Images must come first,
                // because ![foo][f] looks like an anchor.
                text = _DoImages(text);
                text = _DoAnchors(text);
    
                // Make links out of things like `<http://example.com/>`
                // Must come after _DoAnchors(), because you can use < and >
                // delimiters in inline links like [this](<url>).
                text = _DoAutoLinks(text);
    
                text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now
    
                text = _EncodeAmpsAndAngles(text);
                text = options._DoItalicsAndBold ? options._DoItalicsAndBold(text) : _DoItalicsAndBold(text);
    
                // Do hard breaks:
                text = text.replace(/  +\n/g, " <br>\n");
    
                text = pluginHooks.postSpanGamut(text);
    
                return text;
            }
    
            function _EscapeSpecialCharsWithinTagAttributes(text) {
                //
                // Within tags -- meaning between < and > -- encode [\ ` * _] so they
                // don't conflict with their use in Markdown for code, italics and strong.
                //
    
                // Build a regex to find HTML tags and comments.  See Friedl's
                // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
    
                // SE: changed the comment part of the regex
    
                var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;
    
                text = text.replace(regex, function (wholeMatch) {
                    var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
                    tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
                    return tag;
                });
    
                return text;
            }
    
            function _DoAnchors(text) {
                //
                // Turn Markdown link shortcuts into XHTML <a> tags.
                //
                //
                // First, handle reference-style links: [link text] [id]
                //
    
                /*
                text = text.replace(/
                    (                           // wrap whole match in $1
                        \[
                        (
                            (?:
                                \[[^\]]*\]      // allow brackets nested one level
                                |
                                [^\[]           // or anything else
                            )*
                        )
                        \]
    
                        [ ]?                    // one optional space
                        (?:\n[ ]*)?             // one optional newline followed by spaces
    
                        \[
                        (.*?)                   // id = $3
                        \]
                    )
                    ()()()()                    // pad remaining backreferences
                /g, writeAnchorTag);
                */
                text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
    
                //
                // Next, inline-style links: [link text](url "optional title")
                //
    
                /*
                text = text.replace(/
                    (                           // wrap whole match in $1
                        \[
                        (
                            (?:
                                \[[^\]]*\]      // allow brackets nested one level
                                |
                                [^\[\]]         // or anything else
                            )*
                        )
                        \]
                        \(                      // literal paren
                        [ \t]*
                        ()                      // no id, so leave $3 empty
                        <?(                     // href = $4
                            (?:
                                \([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)
                                |
                                [^()\s]
                            )*?
                        )>?
                        [ \t]*
                        (                       // $5
                            (['"])              // quote char = $6
                            (.*?)               // Title = $7
                            \6                  // matching quote
                            [ \t]*              // ignore any spaces/tabs between closing quote and )
                        )?                      // title is optional
                        \)
                    )
                /g, writeAnchorTag);
                */
    
                text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
    
                //
                // Last, handle reference-style shortcuts: [link text]
                // These must come last in case you've also got [link test][1]
                // or [link test](/foo)
                //
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        \[
                        ([^\[\]]+)      // link text = $2; can't contain '[' or ']'
                        \]
                    )
                    ()()()()()          // pad rest of backreferences
                /g, writeAnchorTag);
                */
                text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
    
                return text;
            }
    
            function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
                if (m7 == undefined) m7 = "";
                var whole_match = m1;
                var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs
                var link_id = m3.toLowerCase();
                var url = m4;
                var title = m7;
    
                if (url == "") {
                    if (link_id == "") {
                        // lower-case and turn embedded newlines into spaces
                        link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
                    }
                    url = "#" + link_id;
    
                    if (g_urls.get(link_id) != undefined) {
                        url = g_urls.get(link_id);
                        if (g_titles.get(link_id) != undefined) {
                            title = g_titles.get(link_id);
                        }
                    }
                    else {
                        if (whole_match.search(/\(\s*\)$/m) > -1) {
                            // Special case for explicit empty url
                            url = "";
                        } else {
                            return whole_match;
                        }
                    }
                }
                url = encodeProblemUrlChars(url);
                url = escapeCharacters(url, "*_");
                var result = "<a href=\"" + url + "\"";
    
                if (title != "") {
                    title = attributeEncode(title);
                    title = escapeCharacters(title, "*_");
                    result += " title=\"" + title + "\"";
                }
    
                result += ">" + link_text + "</a>";
    
                return result;
            }
    
            function _DoImages(text) {
                //
                // Turn Markdown image shortcuts into <img> tags.
                //
    
                //
                // First, handle reference-style labeled images: ![alt text][id]
                //
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        !\[
                        (.*?)           // alt text = $2
                        \]
    
                        [ ]?            // one optional space
                        (?:\n[ ]*)?     // one optional newline followed by spaces
    
                        \[
                        (.*?)           // id = $3
                        \]
                    )
                    ()()()()            // pad rest of backreferences
                /g, writeImageTag);
                */
                text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
    
                //
                // Next, handle inline images:  ![alt text](url "optional title")
                // Don't forget: encode * and _
    
                /*
                text = text.replace(/
                    (                   // wrap whole match in $1
                        !\[
                        (.*?)           // alt text = $2
                        \]
                        \s?             // One optional whitespace character
                        \(              // literal paren
                        [ \t]*
                        ()              // no id, so leave $3 empty
                        <?(\S+?)>?      // src url = $4
                        [ \t]*
                        (               // $5
                            (['"])      // quote char = $6
                            (.*?)       // title = $7
                            \6          // matching quote
                            [ \t]*
                        )?              // title is optional
                        \)
                    )
                /g, writeImageTag);
                */
                text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
    
                return text;
            }
    
            function attributeEncode(text) {
                // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)
                // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)
                return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
            }
    
            function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
                var whole_match = m1;
                var alt_text = m2;
                var link_id = m3.toLowerCase();
                var url = m4;
                var title = m7;
    
                if (!title) title = "";
    
                if (url == "") {
                    if (link_id == "") {
                        // lower-case and turn embedded newlines into spaces
                        link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
                    }
                    url = "#" + link_id;
    
                    if (g_urls.get(link_id) != undefined) {
                        url = g_urls.get(link_id);
                        if (g_titles.get(link_id) != undefined) {
                            title = g_titles.get(link_id);
                        }
                    }
                    else {
                        return whole_match;
                    }
                }
    
                alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");
                url = escapeCharacters(url, "*_");
                var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
    
                // attacklab: Markdown.pl adds empty title attributes to images.
                // Replicate this bug.
    
                //if (title != "") {
                title = attributeEncode(title);
                title = escapeCharacters(title, "*_");
                result += " title=\"" + title + "\"";
                //}
    
                result += " />";
    
                return result;
            }
    
            function _DoHeaders(text) {
    
                // Setext-style headers:
                //  Header 1
                //  ========
                //
                //  Header 2
                //  --------
                //
                text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
                    function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }
                );
    
                text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
                    function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }
                );
    
                // atx-style headers:
                //  # Header 1
                //  ## Header 2
                //  ## Header 2 with closing hashes ##
                //  ...
                //  ###### Header 6
                //
    
                /*
                text = text.replace(/
                    ^(\#{1,6})      // $1 = string of #'s
                    [ \t]*
                    (.+?)           // $2 = Header text
                    [ \t]*
                    \#*             // optional closing #'s (not counted)
                    \n+
                /gm, function() {...});
                */
    
                text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
                    function (wholeMatch, m1, m2) {
                        var h_level = m1.length;
                        return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
                    }
                );
    
                return text;
            }
    
            function _DoLists(text, isInsideParagraphlessListItem) {
                //
                // Form HTML ordered (numbered) and unordered (bulleted) lists.
                //
    
                // attacklab: add sentinel to hack around khtml/safari bug:
                // http://bugs.webkit.org/show_bug.cgi?id=11231
                text += "~0";
    
                // Re-usable pattern to match any entirel ul or ol list:
    
                /*
                var whole_list = /
                    (                                   // $1 = whole list
                        (                               // $2
                            [ ]{0,3}                    // attacklab: g_tab_width - 1
                            ([*+-]|\d+[.])              // $3 = first list item marker
                            [ \t]+
                        )
                        [^\r]+?
                        (                               // $4
                            ~0                          // sentinel for workaround; should be $
                            |
                            \n{2,}
                            (?=\S)
                            (?!                         // Negative lookahead for another list item marker
                                [ \t]*
                                (?:[*+-]|\d+[.])[ \t]+
                            )
                        )
                    )
                /g
                */
                var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
    
                if (g_list_level) {
                    text = text.replace(whole_list, function (wholeMatch, m1, m2) {
                        var list = m1;
                        var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
    
                        var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem);
    
                        // Trim any trailing whitespace, to put the closing `</$list_type>`
                        // up on the preceding line, to get it past the current stupid
                        // HTML block parser. This is a hack to work around the terrible
                        // hack that is the HTML block parser.
                        result = result.replace(/\s+$/, "");
                        result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
                        return result;
                    });
                } else {
                    whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
                    text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
                        var runup = m1;
                        var list = m2;
    
                        var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
                        var result = _ProcessListItems(list, list_type);
                        result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
                        return result;
                    });
                }
    
                // attacklab: strip sentinel
                text = text.replace(/~0/, "");
    
                return text;
            }
    
            var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
    
            function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) {
                //
                //  Process the contents of a single ordered or unordered list, splitting it
                //  into individual list items.
                //
                //  list_type is either "ul" or "ol".
    
                // The $g_list_level global keeps track of when we're inside a list.
                // Each time we enter a list, we increment it; when we leave a list,
                // we decrement. If it's zero, we're not in a list anymore.
                //
                // We do this because when we're not inside a list, we want to treat
                // something like this:
                //
                //    I recommend upgrading to version
                //    8. Oops, now this line is treated
                //    as a sub-list.
                //
                // As a single paragraph, despite the fact that the second line starts
                // with a digit-period-space sequence.
                //
                // Whereas when we're inside a list (or sub-list), that line will be
                // treated as the start of a sub-list. What a kludge, huh? This is
                // an aspect of Markdown's syntax that's hard to parse perfectly
                // without resorting to mind-reading. Perhaps the solution is to
                // change the syntax rules such that sub-lists must start with a
                // starting cardinal number; e.g. "1." or "a.".
    
                g_list_level++;
    
                // trim trailing blank lines:
                list_str = list_str.replace(/\n{2,}$/, "\n");
    
                // attacklab: add sentinel to emulate \z
                list_str += "~0";
    
                // In the original attacklab showdown, list_type was not given to this function, and anything
                // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
                //
                //  Markdown          rendered by WMD        rendered by MarkdownSharp
                //  ------------------------------------------------------------------
                //  1. first          1. first               1. first
                //  2. second         2. second              2. second
                //  - third           3. third                   * third
                //
                // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
                // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:
    
                /*
                list_str = list_str.replace(/
                    (^[ \t]*)                       // leading whitespace = $1
                    ({MARKER}) [ \t]+               // list marker = $2
                    ([^\r]+?                        // list item text   = $3
                        (\n+)
                    )
                    (?=
                        (~0 | \2 ({MARKER}) [ \t]+)
                    )
                /gm, function(){...});
                */
    
                var marker = _listItemMarkers[list_type];
                var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
                var last_item_had_a_double_newline = false;
                list_str = list_str.replace(re,
                    function (wholeMatch, m1, m2, m3) {
                        var item = m3;
                        var leading_space = m1;
                        var ends_with_double_newline = /\n\n$/.test(item);
                        var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;
    
                        if (contains_double_newline || last_item_had_a_double_newline) {
                            item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
                        }
                        else {
                            // Recursion for sub-lists:
                            item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true);
                            item = item.replace(/\n$/, ""); // chomp(item)
                            if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones
                                item = _RunSpanGamut(item);
                        }
                        last_item_had_a_double_newline = ends_with_double_newline;
                        return "<li>" + item + "</li>\n";
                    }
                );
    
                // attacklab: strip sentinel
                list_str = list_str.replace(/~0/g, "");
    
                g_list_level--;
                return list_str;
            }
    
            function _DoCodeBlocks(text) {
                //
                //  Process Markdown `<pre><code>` blocks.
                //
    
                /*
                text = text.replace(/
                    (?:\n\n|^)
                    (                               // $1 = the code block -- one or more lines, starting with a space/tab
                        (?:
                            (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
                            .*\n+
                        )+
                    )
                    (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
                /g ,function(){...});
                */
    
                // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
                text += "~0";
    
                text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
                    function (wholeMatch, m1, m2) {
                        var codeblock = m1;
                        var nextChar = m2;
    
                        codeblock = _EncodeCode(_Outdent(codeblock));
                        codeblock = _Detab(codeblock);
                        codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
                        codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    
                        codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
    
                        return "\n\n" + codeblock + "\n\n" + nextChar;
                    }
                );
    
                // attacklab: strip sentinel
                text = text.replace(/~0/, "");
    
                return text;
            }
    
            function hashBlock(text) {
                text = text.replace(/(^\n+|\n+$)/g, "");
                return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
            }
    
            function _DoCodeSpans(text) {
                //
                // * Backtick quotes are used for <code></code> spans.
                //
                // * You can use multiple backticks as the delimiters if you want to
                //   include literal backticks in the code span. So, this input:
                //
                //      Just type ``foo `bar` baz`` at the prompt.
                //
                //   Will translate to:
                //
                //      <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
                //
                //   There's no arbitrary limit to the number of backticks you
                //   can use as delimters. If you need three consecutive backticks
                //   in your code, use four for delimiters, etc.
                //
                // * You can use spaces to get literal backticks at the edges:
                //
                //      ... type `` `bar` `` ...
                //
                //   Turns to:
                //
                //      ... type <code>`bar`</code> ...
                //
    
                /*
                text = text.replace(/
                    (^|[^\\])       // Character before opening ` can't be a backslash
                    (`+)            // $2 = Opening run of `
                    (               // $3 = The code block
                        [^\r]*?
                        [^`]        // attacklab: work around lack of lookbehind
                    )
                    \2              // Matching closer
                    (?!`)
                /gm, function(){...});
                */
    
                text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
                    function (wholeMatch, m1, m2, m3, m4) {
                        var c = m3;
                        c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
                        c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
                        c = _EncodeCode(c);
                        c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.
                        return m1 + "<code>" + c + "</code>";
                    }
                );
    
                return text;
            }
    
            function _EncodeCode(text) {
                //
                // Encode/escape certain characters inside Markdown code runs.
                // The point is that in code, these characters are literals,
                // and lose their special Markdown meanings.
                //
                // Encode all ampersands; HTML entities are not
                // entities within a Markdown code span.
                text = text.replace(/&/g, "&amp;");
    
                // Do the angle bracket song and dance:
                text = text.replace(/</g, "&lt;");
                text = text.replace(/>/g, "&gt;");
    
                // Now, escape characters that are magic in Markdown:
                text = escapeCharacters(text, "\*_{}[]\\", false);
    
                // jj the line above breaks this:
                //---
    
                //* Item
    
                //   1. Subitem
    
                //            special char: *
                //---
    
                return text;
            }
    
            function _DoItalicsAndBold(text) {
    
                // <strong> must go first:
                text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
                "$1<strong>$3</strong>$4");
    
                text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
                "$1<em>$3</em>$4");
    
                return text;
            }
    
            function _DoBlockQuotes(text) {
    
                /*
                text = text.replace(/
                    (                           // Wrap whole match in $1
                        (
                            ^[ \t]*>[ \t]?      // '>' at the start of a line
                            .+\n                // rest of the first line
                            (.+\n)*             // subsequent consecutive lines
                            \n*                 // blanks
                        )+
                    )
                /gm, function(){...});
                */
    
                text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
                    function (wholeMatch, m1) {
                        var bq = m1;
    
                        // attacklab: hack around Konqueror 3.5.4 bug:
                        // "----------bug".replace(/^-/g,"") == "bug"
    
                        bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting
    
                        // attacklab: clean up hack
                        bq = bq.replace(/~0/g, "");
    
                        bq = bq.replace(/^[ \t]+$/gm, "");     // trim whitespace-only lines
                        bq = _RunBlockGamut(bq);             // recurse
    
                        bq = bq.replace(/(^|\n)/g, "$1  ");
                        // These leading spaces screw with <pre> content, so we need to fix that:
                        bq = bq.replace(
                                /(\s*<pre>[^\r]+?<\/pre>)/gm,
                            function (wholeMatch, m1) {
                                var pre = m1;
                                // attacklab: hack around Konqueror 3.5.4 bug:
                                pre = pre.replace(/^  /mg, "~0");
                                pre = pre.replace(/~0/g, "");
                                return pre;
                            });
    
                        return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
                    }
                );
                return text;
            }
    
            function _FormParagraphs(text, doNotUnhash) {
                //
                //  Params:
                //    $text - string to process with html <p> tags
                //
    
                // Strip leading and trailing lines:
                text = text.replace(/^\n+/g, "");
                text = text.replace(/\n+$/g, "");
    
                var grafs = text.split(/\n{2,}/g);
                var grafsOut = [];
    
                var markerRe = /~K(\d+)K/;
    
                //
                // Wrap <p> tags.
                //
                var end = grafs.length;
                for (var i = 0; i < end; i++) {
                    var str = grafs[i];
    
                    // if this is an HTML marker, copy it
                    if (markerRe.test(str)) {
                        grafsOut.push(str);
                    }
                    else if (/\S/.test(str)) {
                        str = _RunSpanGamut(str);
                        str = str.replace(/^([ \t]*)/g, "<p>");
                        str += "</p>"
                        grafsOut.push(str);
                    }
    
                }
                //
                // Unhashify HTML blocks
                //
                if (!doNotUnhash) {
                    end = grafsOut.length;
                    for (var i = 0; i < end; i++) {
                        var foundAny = true;
                        while (foundAny) { // we may need several runs, since the data may be nested
                            foundAny = false;
                            grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {
                                foundAny = true;
                                return g_html_blocks[id];
                            });
                        }
                    }
                }
                return grafsOut.join("\n\n");
            }
    
            function _EncodeAmpsAndAngles(text) {
                // Smart processing for ampersands and angle brackets that need to be encoded.
    
                // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
                //   http://bumppo.net/projects/amputator/
                text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");
    
                // Encode naked <'s
                text = text.replace(/<(?![a-z\/?!]|~D)/gi, "&lt;");
    
                return text;
            }
    
            function _EncodeBackslashEscapes(text) {
                //
                //   Parameter:  String.
                //   Returns:    The string, with after processing the following backslash
                //               escape sequences.
                //
    
                // attacklab: The polite way to do this is with the new
                // escapeCharacters() function:
                //
                //     text = escapeCharacters(text,"\\",true);
                //     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
                //
                // ...but we're sidestepping its use of the (slow) RegExp constructor
                // as an optimization for Firefox.  This function gets called a LOT.
    
                text = text.replace(/\\(\\)/g, escapeCharacters_callback);
                text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
                return text;
            }
    
            var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]",
                charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]",
                autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"),
                endCharRegex = new RegExp(charEndingUrl, "i");
    
            function handleTrailingParens(wholeMatch, lookbehind, protocol, link) {
                if (lookbehind)
                    return wholeMatch;
                if (link.charAt(link.length - 1) !== ")")
                    return "<" + protocol + link + ">";
                var parens = link.match(/[()]/g);
                var level = 0;
                for (var i = 0; i < parens.length; i++) {
                    if (parens[i] === "(") {
                        if (level <= 0)
                            level = 1;
                        else
                            level++;
                    }
                    else {
                        level--;
                    }
                }
                var tail = "";
                if (level < 0) {
                    var re = new RegExp("\\){1," + (-level) + "}$");
                    link = link.replace(re, function (trailingParens) {
                        tail = trailingParens;
                        return "";
                    });
                }
                if (tail) {
                    var lastChar = link.charAt(link.length - 1);
                    if (!endCharRegex.test(lastChar)) {
                        tail = lastChar + tail;
                        link = link.substr(0, link.length - 1);
                    }
                }
                return "<" + protocol + link + ">" + tail;
            }
    
            function _DoAutoLinks(text) {
    
                // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
                // *except* for the <http://www.foo.com> case
    
                // automatically add < and > around unadorned raw hyperlinks
                // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character
                // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor
                // with a <, so there is no risk of overlapping matches.
                text = text.replace(autoLinkRegex, handleTrailingParens);
    
                //  autolink anything like <http://example.com>
    
                var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }
                text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);
    
                // Email addresses: <address@domain.foo>
                /*
                text = text.replace(/
                    <
                    (?:mailto:)?
                    (
                        [-.\w]+
                        \@
                        [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
                    )
                    >
                /gi, _DoAutoLinks_callback());
                */
    
                /* disabling email autolinking, since we don't do that on the server, either
                text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
                    function(wholeMatch,m1) {
                        return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
                    }
                );
                */
                return text;
            }
    
            function _UnescapeSpecialChars(text) {
                //
                // Swap back in all the special characters we've hidden.
                //
                text = text.replace(/~E(\d+)E/g,
                    function (wholeMatch, m1) {
                        var charCodeToReplace = parseInt(m1);
                        return String.fromCharCode(charCodeToReplace);
                    }
                );
                return text;
            }
    
            function _Outdent(text) {
                //
                // Remove one level of line-leading tabs or spaces
                //
    
                // attacklab: hack around Konqueror 3.5.4 bug:
                // "----------bug".replace(/^-/g,"") == "bug"
    
                text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
    
                // attacklab: clean up hack
                text = text.replace(/~0/g, "")
    
                return text;
            }
    
            function _Detab(text) {
                if (!/\t/.test(text))
                    return text;
    
                var spaces = ["    ", "   ", "  ", " "],
                skew = 0,
                v;
    
                return text.replace(/[\n\t]/g, function (match, offset) {
                    if (match === "\n") {
                        skew = offset + 1;
                        return match;
                    }
                    v = (offset - skew) % 4;
                    skew = offset + 1;
                    return spaces[v];
                });
            }
    
            //
            //  attacklab: Utility functions
            //
    
            var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;
    
            // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
            function encodeProblemUrlChars(url) {
                if (!url)
                    return "";
    
                var len = url.length;
    
                return url.replace(_problemUrlChars, function (match, offset) {
                    if (match == "~D") // escape for dollar
                        return "%24";
                    if (match == ":") {
                        //if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
                            return ":"
                    }
                    return "%" + match.charCodeAt(0).toString(16);
                });
            }
    
    
            function escapeCharacters(text, charsToEscape, afterBackslash) {
                // First we have to escape the escape characters so that
                // we can build a character class out of them
                var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
    
                if (afterBackslash) {
                    regexString = "\\\\" + regexString;
                }
    
                var regex = new RegExp(regexString, "g");
                text = text.replace(regex, escapeCharacters_callback);
    
                return text;
            }
    
    
            function escapeCharacters_callback(wholeMatch, m1) {
                var charCodeToEscape = m1.charCodeAt(0);
                return "~E" + charCodeToEscape + "E";
            }
    
        }; // end of the Markdown.Converter constructor
    
    })();
    
    define("libs/Markdown.Converter", function(){});
    
    (function () {
      // A quick way to make sure we're only keeping span-level tags when we need to.
      // This isn't supposed to be foolproof. It's just a quick way to make sure we
      // keep all span-level tags returned by a pagedown converter. It should allow
      // all span-level tags through, with or without attributes.
      var inlineTags = new RegExp(['^(<\\/?(a|abbr|acronym|applet|area|b|basefont|',
                                   'bdo|big|button|cite|code|del|dfn|em|figcaption|',
                                   'font|i|iframe|img|input|ins|kbd|label|map|',
                                   'mark|meter|object|param|progress|q|ruby|rp|rt|s|',
                                   'samp|script|select|small|span|strike|strong|',
                                   'sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|',
                                   '<(br)\\s?\\/?>)$'].join(''), 'i');
    
      /******************************************************************
       * Utility Functions                                              *
       *****************************************************************/
    
      // patch for ie7
      if (!Array.indexOf) {
        Array.prototype.indexOf = function(obj) {
          for (var i = 0; i < this.length; i++) {
            if (this[i] == obj) {
              return i;
            }
          }
          return -1;
        };
      }
    
      function trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
      }
    
      function rtrim(str) {
        return str.replace(/\s+$/g, '');
      }
    
      // Remove one level of indentation from text. Indent is 4 spaces.
      function outdent(text) {
        return text.replace(new RegExp('^(\\t|[ ]{1,4})', 'gm'), '');
      }
    
      function contains(str, substr) {
        return str.indexOf(substr) != -1;
      }
    
      // Sanitize html, removing tags that aren't in the whitelist
      function sanitizeHtml(html, whitelist) {
        return html.replace(/<[^>]*>?/gi, function(tag) {
          return tag.match(whitelist) ? tag : '';
        });
      }
    
      // Merge two arrays, keeping only unique elements.
      function union(x, y) {
        var obj = {};
        for (var i = 0; i < x.length; i++)
           obj[x[i]] = x[i];
        for (i = 0; i < y.length; i++)
           obj[y[i]] = y[i];
        var res = [];
        for (var k in obj) {
          if (obj.hasOwnProperty(k))
            res.push(obj[k]);
        }
        return res;
      }
    
      // JS regexes don't support \A or \Z, so we add sentinels, as Pagedown
      // does. In this case, we add the ascii codes for start of text (STX) and
      // end of text (ETX), an idea borrowed from:
      // https://github.com/tanakahisateru/js-markdown-extra
      function addAnchors(text) {
        if(text.charAt(0) != '\x02')
          text = '\x02' + text;
        if(text.charAt(text.length - 1) != '\x03')
          text = text + '\x03';
        return text;
      }
    
      // Remove STX and ETX sentinels.
      function removeAnchors(text) {
        if(text.charAt(0) == '\x02')
          text = text.substr(1);
        if(text.charAt(text.length - 1) == '\x03')
          text = text.substr(0, text.length - 1);
        return text;
      }
    
      // Convert markdown within an element, retaining only span-level tags
      function convertSpans(text, extra) {
        return sanitizeHtml(convertAll(text, extra), inlineTags);
      }
    
      // Convert internal markdown using the stock pagedown converter
      function convertAll(text, extra) {
        var result = extra.blockGamutHookCallback(text);
        // We need to perform these operations since we skip the steps in the converter
        result = unescapeSpecialChars(result);
        result = result.replace(/~D/g, "$$").replace(/~T/g, "~");
        result = extra.previousPostConversion(result);
        return result;
      }
    
      // Convert escaped special characters
      function processEscapesStep1(text) {
        // Markdown extra adds two escapable characters, `:` and `|`
        return text.replace(/\\\|/g, '~I').replace(/\\:/g, '~i');
      }
      function processEscapesStep2(text) {
        return text.replace(/~I/g, '|').replace(/~i/g, ':');
      }
    
      // Duplicated from PageDown converter
      function unescapeSpecialChars(text) {
        // Swap back in all the special characters we've hidden.
        text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) {
          var charCodeToReplace = parseInt(m1);
          return String.fromCharCode(charCodeToReplace);
        });
        return text;
      }
      
      function slugify(text) {
        return text.toLowerCase()
        .replace(/\s+/g, '-') // Replace spaces with -
        .replace(/[^\w\-]+/g, '') // Remove all non-word chars
        .replace(/\-\-+/g, '-') // Replace multiple - with single -
        .replace(/^-+/, '') // Trim - from start of text
        .replace(/-+$/, ''); // Trim - from end of text
      }
    
      /*****************************************************************************
       * Markdown.Extra *
       ****************************************************************************/
    
      Markdown.Extra = function() {
        // For converting internal markdown (in tables for instance).
        // This is necessary since these methods are meant to be called as
        // preConversion hooks, and the Markdown converter passed to init()
        // won't convert any markdown contained in the html tags we return.
        this.converter = null;
    
        // Stores html blocks we generate in hooks so that
        // they're not destroyed if the user is using a sanitizing converter
        this.hashBlocks = [];
        
        // Stores footnotes
        this.footnotes = {};
        this.usedFootnotes = [];
    
        // Special attribute blocks for fenced code blocks and headers enabled.
        this.attributeBlocks = false;
    
        // Fenced code block options
        this.googleCodePrettify = false;
        this.highlightJs = false;
    
        // Table options
        this.tableClass = '';
    
        this.tabWidth = 4;
      };
    
      Markdown.Extra.init = function(converter, options) {
        // Each call to init creates a new instance of Markdown.Extra so it's
        // safe to have multiple converters, with different options, on a single page
        var extra = new Markdown.Extra();
        var postNormalizationTransformations = [];
        var preBlockGamutTransformations = [];
        var postSpanGamutTransformations = [];
        var postConversionTransformations = ["unHashExtraBlocks"];
    
        options = options || {};
        options.extensions = options.extensions || ["all"];
        if (contains(options.extensions, "all")) {
          options.extensions = ["tables", "fenced_code_gfm", "def_list", "attr_list", "footnotes", "smartypants", "strikethrough", "newlines"];
        }
        preBlockGamutTransformations.push("wrapHeaders");
        if (contains(options.extensions, "attr_list")) {
          postNormalizationTransformations.push("hashFcbAttributeBlocks");
          preBlockGamutTransformations.push("hashHeaderAttributeBlocks");
          postConversionTransformations.push("applyAttributeBlocks");
          extra.attributeBlocks = true;
        }
        if (contains(options.extensions, "fenced_code_gfm")) {
          // This step will convert fcb inside list items and blockquotes
          preBlockGamutTransformations.push("fencedCodeBlocks");
          // This extra step is to prevent html blocks hashing and link definition/footnotes stripping inside fcb
          postNormalizationTransformations.push("fencedCodeBlocks");
        }
        if (contains(options.extensions, "tables")) {
          preBlockGamutTransformations.push("tables");
        }
        if (contains(options.extensions, "def_list")) {
          preBlockGamutTransformations.push("definitionLists");
        }
        if (contains(options.extensions, "footnotes")) {
          postNormalizationTransformations.push("stripFootnoteDefinitions");
          preBlockGamutTransformations.push("doFootnotes");
          postConversionTransformations.push("printFootnotes");
        }
        if (contains(options.extensions, "smartypants")) {
          postConversionTransformations.push("runSmartyPants");
        }
        if (contains(options.extensions, "strikethrough")) {
          postSpanGamutTransformations.push("strikethrough");
        }
        if (contains(options.extensions, "newlines")) {
          postSpanGamutTransformations.push("newlines");
        }
        
        converter.hooks.chain("postNormalization", function(text) {
          return extra.doTransform(postNormalizationTransformations, text) + '\n';
        });
    
        converter.hooks.chain("preBlockGamut", function(text, blockGamutHookCallback) {
          // Keep a reference to the block gamut callback to run recursively
          extra.blockGamutHookCallback = blockGamutHookCallback;
          text = processEscapesStep1(text);
          text = extra.doTransform(preBlockGamutTransformations, text) + '\n';
          text = processEscapesStep2(text);
          return text;
        });
    
        converter.hooks.chain("postSpanGamut", function(text) {
          return extra.doTransform(postSpanGamutTransformations, text);
        });
    
        // Keep a reference to the hook chain running before doPostConversion to apply on hashed extra blocks
        extra.previousPostConversion = converter.hooks.postConversion;
        converter.hooks.chain("postConversion", function(text) {
          text = extra.doTransform(postConversionTransformations, text);
          // Clear state vars that may use unnecessary memory
          extra.hashBlocks = [];
          extra.footnotes = {};
          extra.usedFootnotes = [];
          return text;
        });
    
        if ("highlighter" in options) {
          extra.googleCodePrettify = options.highlighter === 'prettify';
          extra.highlightJs = options.highlighter === 'highlight';
        }
    
        if ("table_class" in options) {
          extra.tableClass = options.table_class;
        }
    
        extra.converter = converter;
    
        // Caller usually won't need this, but it's handy for testing.
        return extra;
      };
    
      // Do transformations
      Markdown.Extra.prototype.doTransform = function(transformations, text) {
        for(var i = 0; i < transformations.length; i++)
          text = this[transformations[i]](text);
        return text;
      };
    
      // Return a placeholder containing a key, which is the block's index in the
      // hashBlocks array. We wrap our output in a <p> tag here so Pagedown won't.
      Markdown.Extra.prototype.hashExtraBlock = function(block) {
        return '\n<p>~X' + (this.hashBlocks.push(block) - 1) + 'X</p>\n';
      };
      Markdown.Extra.prototype.hashExtraInline = function(block) {
        return '~X' + (this.hashBlocks.push(block) - 1) + 'X';
      };
      
      // Replace placeholder blocks in `text` with their corresponding
      // html blocks in the hashBlocks array.
      Markdown.Extra.prototype.unHashExtraBlocks = function(text) {
        var self = this;
        function recursiveUnHash() {
          var hasHash = false;
          text = text.replace(/(?:<p>)?~X(\d+)X(?:<\/p>)?/g, function(wholeMatch, m1) {
            hasHash = true;
            var key = parseInt(m1, 10);
            return self.hashBlocks[key];
          });
          if(hasHash === true) {
            recursiveUnHash();
          }
        }
        recursiveUnHash();
        return text;
      };
      
      // Wrap headers to make sure they won't be in def lists
      Markdown.Extra.prototype.wrapHeaders = function(text) {
        function wrap(text) {
          return '\n' + text + '\n';
        }
        text = text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm, wrap);
        text = text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm, wrap);
        text = text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm, wrap);
        return text;
      };
    
    
      /******************************************************************
       * Attribute Blocks                                               *
       *****************************************************************/
    
      // TODO: use sentinels. Should we just add/remove them in doConversion?
      // TODO: better matches for id / class attributes
      var attrBlock = "\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}";
      var hdrAttributesA = new RegExp("^(#{1,6}.*#{0,6})[ \\t]+" + attrBlock + "[ \\t]*(?:\\n|0x03)", "gm");
      var hdrAttributesB = new RegExp("^(.*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
        "(?=[\\-|=]+\\s*(?:\\n|0x03))", "gm"); // underline lookahead
      var fcbAttributes =  new RegExp("^(```[^`\\n]*)[ \\t]+" + attrBlock + "[ \\t]*\\n" +
        "(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))", "gm");
          
      // Extract headers attribute blocks, move them above the element they will be
      // applied to, and hash them for later.
      Markdown.Extra.prototype.hashHeaderAttributeBlocks = function(text) {
        
        var self = this;
        function attributeCallback(wholeMatch, pre, attr) {
          return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
        }
    
        text = text.replace(hdrAttributesA, attributeCallback);  // ## headers
        text = text.replace(hdrAttributesB, attributeCallback);  // underline headers
        return text;
      };
      
      // Extract FCB attribute blocks, move them above the element they will be
      // applied to, and hash them for later.
      Markdown.Extra.prototype.hashFcbAttributeBlocks = function(text) {
        // TODO: use sentinels. Should we just add/remove them in doConversion?
        // TODO: better matches for id / class attributes
    
        var self = this;
        function attributeCallback(wholeMatch, pre, attr) {
          return '<p>~XX' + (self.hashBlocks.push(attr) - 1) + 'XX</p>\n' + pre + "\n";
        }
    
        return text.replace(fcbAttributes, attributeCallback);
      };
    
      Markdown.Extra.prototype.applyAttributeBlocks = function(text) {
        var self = this;
        var blockRe = new RegExp('<p>~XX(\\d+)XX</p>[\\s]*' +
                                 '(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?</\\2>))', "gm");
        text = text.replace(blockRe, function(wholeMatch, k, tag, cls, rest) {
          if (!tag) // no following header or fenced code block.
            return '';
    
          // get attributes list from hash
          var key = parseInt(k, 10);
          var attributes = self.hashBlocks[key];
    
          // get id
          var id = attributes.match(/#[^\s#.]+/g) || [];
          var idStr = id[0] ? ' id="' + id[0].substr(1, id[0].length - 1) + '"' : '';
    
          // get classes and merge with existing classes
          var classes = attributes.match(/\.[^\s#.]+/g) || [];
          for (var i = 0; i < classes.length; i++) // Remove leading dot
            classes[i] = classes[i].substr(1, classes[i].length - 1);
    
          var classStr = '';
          if (cls)
            classes = union(classes, [cls]);
    
          if (classes.length > 0)
            classStr = ' class="' + classes.join(' ') + '"';
    
          return "<" + tag + idStr + classStr + rest;
        });
    
        return text;
      };
    
      /******************************************************************
       * Tables                                                         *
       *****************************************************************/
    
      // Find and convert Markdown Extra tables into html.
      Markdown.Extra.prototype.tables = function(text) {
        var self = this;
    
        var leadingPipe = new RegExp(
          ['^'                         ,
           '[ ]{0,3}'                  , // Allowed whitespace
           '[|]'                       , // Initial pipe
           '(.+)\\n'                   , // $1: Header Row
    
           '[ ]{0,3}'                  , // Allowed whitespace
           '[|]([ ]*[-:]+[-| :]*)\\n'  , // $2: Separator
    
           '('                         , // $3: Table Body
             '(?:[ ]*[|].*\\n?)*'      , // Table rows
           ')',
           '(?:\\n|$)'                   // Stop at final newline
          ].join(''),
          'gm'
        );
    
        var noLeadingPipe = new RegExp(
          ['^'                         ,
           '[ ]{0,3}'                  , // Allowed whitespace
           '(\\S.*[|].*)\\n'           , // $1: Header Row
    
           '[ ]{0,3}'                  , // Allowed whitespace
           '([-:]+[ ]*[|][-| :]*)\\n'  , // $2: Separator
    
           '('                         , // $3: Table Body
             '(?:.*[|].*\\n?)*'        , // Table rows
           ')'                         ,
           '(?:\\n|$)'                   // Stop at final newline
          ].join(''),
          'gm'
        );
    
        text = text.replace(leadingPipe, doTable);
        text = text.replace(noLeadingPipe, doTable);
    
        // $1 = header, $2 = separator, $3 = body
        function doTable(match, header, separator, body, offset, string) {
          // remove any leading pipes and whitespace
          header = header.replace(/^ *[|]/m, '');
          separator = separator.replace(/^ *[|]/m, '');
          body = body.replace(/^ *[|]/gm, '');
    
          // remove trailing pipes and whitespace
          header = header.replace(/[|] *$/m, '');
          separator = separator.replace(/[|] *$/m, '');
          body = body.replace(/[|] *$/gm, '');
    
          // determine column alignments
          alignspecs = separator.split(/ *[|] */);
          align = [];
          for (var i = 0; i < alignspecs.length; i++) {
            var spec = alignspecs[i];
            if (spec.match(/^ *-+: *$/m))
              align[i] = ' align="right"';
            else if (spec.match(/^ *:-+: *$/m))
              align[i] = ' align="center"';
            else if (spec.match(/^ *:-+ *$/m))
              align[i] = ' align="left"';
            else align[i] = '';
          }
    
          // TODO: parse spans in header and rows before splitting, so that pipes
          // inside of tags are not interpreted as separators
          var headers = header.split(/ *[|] */);
          var colCount = headers.length;
    
          // build html
          var cls = self.tableClass ? ' class="' + self.tableClass + '"' : '';
          var html = ['<table', cls, '>\n', '<thead>\n', '<tr>\n'].join('');
    
          // build column headers.
          for (i = 0; i < colCount; i++) {
            var headerHtml = convertSpans(trim(headers[i]), self);
            html += ["  <th", align[i], ">", headerHtml, "</th>\n"].join('');
          }
          html += "</tr>\n</thead>\n";
    
          // build rows
          var rows = body.split('\n');
          for (i = 0; i < rows.length; i++) {
            if (rows[i].match(/^\s*$/)) // can apply to final row
              continue;
    
            // ensure number of rowCells matches colCount
            var rowCells = rows[i].split(/ *[|] */);
            var lenDiff = colCount - rowCells.length;
            for (var j = 0; j < lenDiff; j++)
              rowCells.push('');
    
            html += "<tr>\n";
            for (j = 0; j < colCount; j++) {
              var colHtml = convertSpans(trim(rowCells[j]), self);
              html += ["  <td", align[j], ">", colHtml, "</td>\n"].join('');
            }
            html += "</tr>\n";
          }
    
          html += "</table>\n";
    
          // replace html with placeholder until postConversion step
          return self.hashExtraBlock(html);
        }
    
        return text;
      };
    
    
      /******************************************************************
       * Footnotes                                                      *
       *****************************************************************/
      
      // Strip footnote, store in hashes.
      Markdown.Extra.prototype.stripFootnoteDefinitions = function(text) {
        var self = this;
    
        text = text.replace(
          /\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,
          function(wholeMatch, m1, m2) {
            m1 = slugify(m1);
            m2 += "\n";
            m2 = m2.replace(/^[ ]{0,3}/g, "");
            self.footnotes[m1] = m2;
            return "\n";
          });
    
        return text;
      };
      
    
      // Find and convert footnotes references.
      Markdown.Extra.prototype.doFootnotes = function(text) {
        var self = this;
        if(self.isConvertingFootnote === true) {
          return text;
        }
    
        var footnoteCounter = 0;
        text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
          var id = slugify(m1);
          var footnote = self.footnotes[id];
          if (footnote === undefined) {
            return wholeMatch;
          }
          footnoteCounter++;
          self.usedFootnotes.push(id);
          var html = '<a href="#fn:' + id + '" id="fnref:' + id
          + '" title="See footnote" class="footnote">' + footnoteCounter
          + '</a>';
          return self.hashExtraInline(html);
        });
    
        return text;
      };
    
      // Print footnotes at the end of the document
      Markdown.Extra.prototype.printFootnotes = function(text) {
        var self = this;
    
        if (self.usedFootnotes.length === 0) {
          return text;
        }
    
        text += '\n\n<div class="footnotes">\n<hr>\n<ol>\n\n';
        for(var i=0; i<self.usedFootnotes.length; i++) {
          var id = self.usedFootnotes[i];
          var footnote = self.footnotes[id];
          self.isConvertingFootnote = true;
          var formattedfootnote = convertSpans(footnote, self);
          delete self.isConvertingFootnote;
          text += '<li id="fn:'
            + id
            + '">'
            + formattedfootnote
            + ' <a href="#fnref:'
            + id
            + '" title="Return to article" class="reversefootnote">&#8617;</a></li>\n\n';
        }
        text += '</ol>\n</div>';
        return text;
      };
      
      
      /******************************************************************
      * Fenced Code Blocks  (gfm)                                       *
      ******************************************************************/
    
      // Find and convert gfm-inspired fenced code blocks into html.
      Markdown.Extra.prototype.fencedCodeBlocks = function(text) {
        function encodeCode(code) {
          code = code.replace(/&/g, "&amp;");
          code = code.replace(/</g, "&lt;");
          code = code.replace(/>/g, "&gt;");
          // These were escaped by PageDown before postNormalization 
          code = code.replace(/~D/g, "$$");
          code = code.replace(/~T/g, "~");
          return code;
        }
    
        var self = this;
        text = text.replace(/(?:^|\n)```([^`\n]*)\n([\s\S]*?)\n```[ \t]*(?=\n)/g, function(match, m1, m2) {
          var language = trim(m1), codeblock = m2;
    
          // adhere to specified options
          var preclass = self.googleCodePrettify ? ' class="prettyprint"' : '';
          var codeclass = '';
          if (language) {
            if (self.googleCodePrettify || self.highlightJs) {
              // use html5 language- class names. supported by both prettify and highlight.js
              codeclass = ' class="language-' + language + '"';
            } else {
              codeclass = ' class="' + language + '"';
            }
          }
    
          var html = ['<pre', preclass, '><code', codeclass, '>',
                      encodeCode(codeblock), '</code></pre>'].join('');
    
          // replace codeblock with placeholder until postConversion step
          return self.hashExtraBlock(html);
        });
    
        return text;
      };
    
    
      /******************************************************************
      * SmartyPants                                                     *
      ******************************************************************/
      
      Markdown.Extra.prototype.educatePants = function(text) {
        var self = this;
        var result = '';
        var blockOffset = 0;
        // Here we parse HTML in a very bad manner
        text.replace(/(?:<!--[\s\S]*?-->)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g, function(wholeMatch, m1, m2, m3, m4, m5, offset) {
          var token = text.substring(blockOffset, offset);
          result += self.applyPants(token);
          self.smartyPantsLastChar = result.substring(result.length - 1);
          blockOffset = offset + wholeMatch.length;
          if(!m1) {
            // Skip commentary
            result += wholeMatch;
            return;
          }
          // Skip special tags
          if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)) {
            m4 = self.educatePants(m4);
          }
          else {
            self.smartyPantsLastChar = m4.substring(m4.length - 1);
          }
          result += m1 + m2 + m3 + m4 + m5;
        });
        var lastToken = text.substring(blockOffset);
        result += self.applyPants(lastToken);
        self.smartyPantsLastChar = result.substring(result.length - 1);
        return result;
      };
        
      function revertPants(wholeMatch, m1) {
        var blockText = m1;
        blockText = blockText.replace(/&\#8220;/g, "\"");
        blockText = blockText.replace(/&\#8221;/g, "\"");
        blockText = blockText.replace(/&\#8216;/g, "'");
        blockText = blockText.replace(/&\#8217;/g, "'");
        blockText = blockText.replace(/&\#8212;/g, "---");
        blockText = blockText.replace(/&\#8211;/g, "--");
        blockText = blockText.replace(/&\#8230;/g, "...");
        return blockText;
      }
      
      Markdown.Extra.prototype.applyPants = function(text) {
        // Dashes
        text = text.replace(/---/g, "&#8212;").replace(/--/g, "&#8211;");
        // Ellipses
        text = text.replace(/\.\.\./g, "&#8230;").replace(/\.\s\.\s\./g, "&#8230;");
        // Backticks
        text = text.replace(/``/g, "&#8220;").replace (/''/g, "&#8221;");
        
        if(/^'$/.test(text)) {
          // Special case: single-character ' token
          if(/\S/.test(this.smartyPantsLastChar)) {
            return "&#8217;";
          }
          return "&#8216;";
        }
        if(/^"$/.test(text)) {
          // Special case: single-character " token
          if(/\S/.test(this.smartyPantsLastChar)) {
            return "&#8221;";
          }
          return "&#8220;";
        }
    
        // Special case if the very first character is a quote
        // followed by punctuation at a non-word-break. Close the quotes by brute force:
        text = text.replace (/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "&#8217;");
        text = text.replace (/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/, "&#8221;");
    
        // Special case for double sets of quotes, e.g.:
        //   <p>He said, "'Quoted' words in a larger quote."</p>
        text = text.replace(/"'(?=\w)/g, "&#8220;&#8216;");
        text = text.replace(/'"(?=\w)/g, "&#8216;&#8220;");
    
        // Special case for decade abbreviations (the '80s):
        text = text.replace(/'(?=\d{2}s)/g, "&#8217;");
        
        // Get most opening single quotes:
        text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g, "$1&#8216;");
        
        // Single closing quotes:
        text = text.replace(/([^\s\[\{\(\-])'/g, "$1&#8217;");
        text = text.replace(/'(?=\s|s\b)/g, "&#8217;");
    
        // Any remaining single quotes should be opening ones:
        text = text.replace(/'/g, "&#8216;");
        
        // Get most opening double quotes:
        text = text.replace(/(\s|&nbsp;|--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g, "$1&#8220;");
        
        // Double closing quotes:
        text = text.replace(/([^\s\[\{\(\-])"/g, "$1&#8221;");
        text = text.replace(/"(?=\s)/g, "&#8221;");
        
        // Any remaining quotes should be opening ones.
        text = text.replace(/"/ig, "&#8220;");
        return text;
      };
    
      // Find and convert markdown extra definition lists into html.
      Markdown.Extra.prototype.runSmartyPants = function(text) {
        this.smartyPantsLastChar = '';
        text = this.educatePants(text);
        // Clean everything inside html tags (some of them may have been converted due to our rough html parsing)
        text = text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g, revertPants);
        return text;
      };
      
      /******************************************************************
      * Definition Lists                                                *
      ******************************************************************/
    
      // Find and convert markdown extra definition lists into html.
      Markdown.Extra.prototype.definitionLists = function(text) {
        var wholeList = new RegExp(
          ['(\\x02\\n?|\\n\\n)'          ,
           '(?:'                         ,
             '('                         , // $1 = whole list
               '('                       , // $2
                 '[ ]{0,3}'              ,
                 '((?:[ \\t]*\\S.*\\n)+)', // $3 = defined term
                 '\\n?'                  ,
                 '[ ]{0,3}:[ ]+'         , // colon starting definition
               ')'                       ,
               '([\\s\\S]+?)'            ,
               '('                       , // $4
                   '(?=\\0x03)'          , // \z
                 '|'                     ,
                   '(?='                 ,
                     '\\n{2,}'           ,
                     '(?=\\S)'           ,
                     '(?!'               , // Negative lookahead for another term
                       '[ ]{0,3}'        ,
                       '(?:\\S.*\\n)+?'  , // defined term
                       '\\n?'            ,
                       '[ ]{0,3}:[ ]+'   , // colon starting definition
                     ')'                 ,
                     '(?!'               , // Negative lookahead for another definition
                       '[ ]{0,3}:[ ]+'   , // colon starting definition
                     ')'                 ,
                   ')'                   ,
               ')'                       ,
             ')'                         ,
           ')'
          ].join(''),
          'gm'
        );
    
        var self = this;
        text = addAnchors(text);
    
        text = text.replace(wholeList, function(match, pre, list) {
          var result = trim(self.processDefListItems(list));
          result = "<dl>\n" + result + "\n</dl>";
          return pre + self.hashExtraBlock(result) + "\n\n";
        });
    
        return removeAnchors(text);
      };
    
      // Process the contents of a single definition list, splitting it
      // into individual term and definition list items.
      Markdown.Extra.prototype.processDefListItems = function(listStr) {
        var self = this;
    
        var dt = new RegExp(
          ['(\\x02\\n?|\\n\\n+)'    , // leading line
           '('                      , // definition terms = $1
             '[ ]{0,3}'             , // leading whitespace
             '(?![:][ ]|[ ])'       , // negative lookahead for a definition
                                      //   mark (colon) or more whitespace
             '(?:\\S.*\\n)+?'       , // actual term (not whitespace)
           ')'                      ,
           '(?=\\n?[ ]{0,3}:[ ])'     // lookahead for following line feed
          ].join(''),                 //   with a definition mark
          'gm'
        );
    
        var dd = new RegExp(
          ['\\n(\\n+)?'              , // leading line = $1
           '('                       , // marker space = $2
             '[ ]{0,3}'              , // whitespace before colon
             '[:][ ]+'               , // definition mark (colon)
           ')'                       ,
           '([\\s\\S]+?)'            , // definition text = $3
           '(?=\\n*'                 , // stop at next definition mark,
             '(?:'                   , // next term or end of text
               '\\n[ ]{0,3}[:][ ]|'  ,
               '<dt>|\\x03'          , // \z
             ')'                     ,
           ')'
          ].join(''),
          'gm'
        );
    
        listStr = addAnchors(listStr);
        // trim trailing blank lines:
        listStr = listStr.replace(/\n{2,}(?=\\x03)/, "\n");
    
        // Process definition terms.
        listStr = listStr.replace(dt, function(match, pre, termsStr) {
          var terms = trim(termsStr).split("\n");
          var text = '';
          for (var i = 0; i < terms.length; i++) {
            var term = terms[i];
            // process spans inside dt
            term = convertSpans(trim(term), self);
            text += "\n<dt>" + term + "</dt>";
          }
          return text + "\n";
        });
    
        // Process actual definitions.
        listStr = listStr.replace(dd, function(match, leadingLine, markerSpace, def) {
          if (leadingLine || def.match(/\n{2,}/)) {
            // replace marker with the appropriate whitespace indentation
            def = Array(markerSpace.length + 1).join(' ') + def;
            // process markdown inside definition
            // TODO?: currently doesn't apply extensions
            def = outdent(def) + "\n\n";
            def = "\n" + convertAll(def, self) + "\n";
          } else {
            // convert span-level markdown inside definition
            def = rtrim(def);
            def = convertSpans(outdent(def), self);
          }
    
          return "\n<dd>" + def + "</dd>\n";
        });
    
        return removeAnchors(listStr);
      };
    
    
      /***********************************************************
      * Strikethrough                                            *
      ************************************************************/
    
      Markdown.Extra.prototype.strikethrough = function(text) {
        // Pretty much duplicated from _DoItalicsAndBold
        return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g,
          "$1<del>$2</del>$3");
      };
    
    
      /***********************************************************
      * New lines                                                *
      ************************************************************/
    
      Markdown.Extra.prototype.newlines = function(text) {
        // We have to ignore already converted newlines and line breaks in sub-list items
        return text.replace(/(<(?:br|\/li)>)?\n/g, function(wholeMatch, previousTag) {
          return previousTag ? wholeMatch : " <br>\n";
        });
      };
      
    })();
    
    
    define("pagedownExtra", function(){});
    
    /*globals Markdown */
    define('extensions/markdownExtra',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	"text!html/markdownExtraSettingsBlock.html",
    	'google-code-prettify',
    	// 'highlightjs',
    	'crel',
    	'pagedownExtra'
    ], function( _, utils, logger, Extension, markdownExtraSettingsBlockHTML, prettify) {
    
    	var markdownExtra = new Extension("markdownExtra", "Markdown Extra", true);
    	markdownExtra.settingsBlock = markdownExtraSettingsBlockHTML;
    	markdownExtra.defaultConfig = {
    		extensions: [
    			"fenced_code_gfm",
    			"tables",
    			"def_list",
    			"attr_list",
    			"footnotes",
    			"smartypants",
    			"strikethrough",
    			"newlines"
    		],
    		intraword: true,
    		comments: true,
    		highlighter: "highlight"
    	};
    
    	markdownExtra.onLoadSettings = function() {
    		function hasExtension(extensionName) {
    			return _.some(markdownExtra.config.extensions, function(extension) {
    				return extension == extensionName;
    			});
    		}
    
    		utils.setInputChecked("#input-markdownextra-fencedcodegfm", hasExtension("fenced_code_gfm"));
    		utils.setInputChecked("#input-markdownextra-tables", hasExtension("tables"));
    		utils.setInputChecked("#input-markdownextra-deflist", hasExtension("def_list"));
    		utils.setInputChecked("#input-markdownextra-attrlist", hasExtension("attr_list"));
    		utils.setInputChecked("#input-markdownextra-footnotes", hasExtension("footnotes"));
    		utils.setInputChecked("#input-markdownextra-smartypants", hasExtension("smartypants"));
    		utils.setInputChecked("#input-markdownextra-strikethrough", hasExtension("strikethrough"));
    		utils.setInputChecked("#input-markdownextra-newlines", hasExtension("newlines"));
    		utils.setInputChecked("#input-markdownextra-intraword", markdownExtra.config.intraword);
    		utils.setInputChecked("#input-markdownextra-comments", markdownExtra.config.comments);
    		utils.setInputValue("#input-markdownextra-highlighter", markdownExtra.config.highlighter);
    	};
    
    	markdownExtra.onSaveSettings = function(newConfig) {
    		newConfig.extensions = [];
    		utils.getInputChecked("#input-markdownextra-fencedcodegfm") && newConfig.extensions.push("fenced_code_gfm");
    		utils.getInputChecked("#input-markdownextra-tables") && newConfig.extensions.push("tables");
    		utils.getInputChecked("#input-markdownextra-deflist") && newConfig.extensions.push("def_list");
    		utils.getInputChecked("#input-markdownextra-attrlist") && newConfig.extensions.push("attr_list");
    		utils.getInputChecked("#input-markdownextra-footnotes") && newConfig.extensions.push("footnotes");
    		utils.getInputChecked("#input-markdownextra-smartypants") && newConfig.extensions.push("smartypants");
    		utils.getInputChecked("#input-markdownextra-strikethrough") && newConfig.extensions.push("strikethrough");
    		utils.getInputChecked("#input-markdownextra-newlines") && newConfig.extensions.push("newlines");
    		newConfig.intraword = utils.getInputChecked("#input-markdownextra-intraword");
    		newConfig.comments = utils.getInputChecked("#input-markdownextra-comments");
    		newConfig.highlighter = utils.getInputValue("#input-markdownextra-highlighter");
    	};
    
    	var eventMgr;
    	markdownExtra.onEventMgrCreated = function(eventMgrParameter) {
    		eventMgr = eventMgrParameter;
    	};
    
    	var previewContentsElt;
    	markdownExtra.onReady = function() {
    		previewContentsElt = document.getElementById('preview-contents');
    	};
    
    	markdownExtra.onPagedownConfigure = function(editor) {
    		var converter = editor.getConverter();
    		var extraOptions = {
    			extensions: markdownExtra.config.extensions,
    			highlighter: "prettify"
    		};
    
    		if(markdownExtra.config.intraword === true) {
    			var converterOptions = {
    				_DoItalicsAndBold: function(text) {
    					text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1<strong>$3</strong>");
    					text = text.replace(/([^\w*]|^)(\*|_)(?=\S)(.+?)(?=\S)\2(?=[^\w*]|$)/g, "$1<em>$3</em>");
    					// Redo bold to handle _**word**_
    					text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1<strong>$3</strong>");
    					return text;
    				}
    			};
    			converter.setOptions(converterOptions);
    		}
    		if(markdownExtra.config.comments === true) {
    			converter.hooks.chain("postConversion", function(text) {
    				return text.replace(/<!--.*?-->/g, function(wholeMatch) {
    					return wholeMatch.replace(/^<!---(.+?)-?-->$/, ' <span class="comment label label-danger">$1</span> ');
    				});
    			});
    		}
    		/*
    		if(markdownExtra.config.highlighter == "highlight") {
    			var previewContentsElt = document.getElementById('preview-contents');
    			editor.hooks.chain("onPreviewRefresh", function() {
    				_.each(previewContentsElt.querySelectorAll('.prettyprint > code'), function(elt) {
    					!elt.highlighted && hljs.highlightBlock(elt);
    					elt.highlighted = true;
    				});
    			});
    		}
    		else if(markdownExtra.config.highlighter == "prettify") {
    			editor.hooks.chain("onPreviewRefresh", prettify.prettyPrint);
    		}
    		*/
    		editor.hooks.chain("onPreviewRefresh", function() {
    			$('#preview-contents pre').addClass('prettyprint linenums');
    			prettify.prettyPrint();
    		});
    		Markdown.Extra.init(converter, extraOptions);
    	};
    
    	return markdownExtra;
    });
    
    define('text!html/mathJaxSettingsBlock.html',[],function () { return '<p>Allows StackEdit to interpret LaTeX mathematical expressions.</p>\n<div class="form-horizontal">\n    <div class="form-group">\n        <label class="col-sm-4 control-label"\n            for="input-mathjax-config-tex">TeX configuration</label>\n        <div class="col-sm-7">\n            <input type="text" id="input-mathjax-config-tex" class="form-control">\n        </div>\n    </div>\n    <div class="form-group">\n        <label class="col-sm-4 control-label"\n            for="input-mathjax-config-tex2jax">tex2jax configuration</label>\n        <div class="col-sm-7">\n            <input type="text" id="input-mathjax-config-tex2jax" class="form-control">\n        </div>\n    </div>\n</div>\n<span class="help-block pull-right"><a target="_blank" href="http://docs.mathjax.org/en/latest/options/index.html">More info</a></span>';});
    
    define('text!libs/mathjax_config.js',[],function () { return 'MathJax.Hub.Config({\n\tskipStartupTypeset: true,\n    "HTML-CSS": {\n        preferredFont: "TeX",\n        availableFonts: [\n            "STIX",\n            "TeX"\n        ],\n        linebreaks: {\n            automatic: true\n        },\n        EqnChunk: 10,\n        imageFont: null\n    },\n    tex2jax: <%= tex2jax || \'{ inlineMath: [["$","$"],["\\\\\\\\\\\\\\\\(","\\\\\\\\\\\\\\\\)"]], displayMath: [["$$","$$"],["\\\\\\\\[","\\\\\\\\]"]], processEscapes: true }\' %>,\n    TeX: $.extend({\n        noUndefined: {\n            attributes: {\n                mathcolor: "red",\n                mathbackground: "#FFEEEE",\n                mathsize: "90%"\n            }\n        },\n        Safe: {\n            allow: {\n                URLs: "safe",\n                classes: "safe",\n                cssIDs: "safe",\n                styles: "safe",\n                fontsize: "all"\n            }\n        }\n    }, <%= tex %>),\n    messageStyle: "none"\n});\n';});
    
    define('libs/mathjax_init',[
        "settings",
        "text!libs/mathjax_config.js"
    ], function(settings, mathjaxConfigJS) {
        var script = document.createElement('script');
        script.type = 'text/x-mathjax-config';
        script.innerHTML = _.template(mathjaxConfigJS, {
            tex: settings.extensionSettings.mathJax ? settings.extensionSettings.mathJax.tex : 'undefined',
            tex2jax: settings.extensionSettings.mathJax ? settings.extensionSettings.mathJax.tex2jax : undefined
        });
        document.getElementsByTagName('head')[0].appendChild(script);
    });
    /*defines MathJax */
    define('extensions/mathJax',[
    	"utils",
    	"classes/Extension",
    	"text!html/mathJaxSettingsBlock.html",
    	"mathjax"
    ], function(utils, Extension, mathJaxSettingsBlockHTML) {
    
    	var mathJax = new Extension("mathJax", "MathJax", true);
    	mathJax.settingsBlock = mathJaxSettingsBlockHTML;
    	mathJax.defaultConfig = {
    		tex    : "{}",
    		tex2jax: '{ inlineMath: [["$","$"],["\\\\\\\\(","\\\\\\\\)"]], displayMath: [["$$","$$"],["\\\\[","\\\\]"]], processEscapes: true }'
    	};
    
    	mathJax.onLoadSettings = function() {
    		utils.setInputValue("#input-mathjax-config-tex", mathJax.config.tex);
    		utils.setInputValue("#input-mathjax-config-tex2jax", mathJax.config.tex2jax);
    	};
    
    	mathJax.onSaveSettings = function(newConfig, event) {
    		newConfig.tex = utils.getInputJsValue("#input-mathjax-config-tex", event);
    		newConfig.tex2jax = utils.getInputJsValue("#input-mathjax-config-tex2jax", event);
    	};
    
    	/*jshint ignore:start */
    	mathJax.onPagedownConfigure = function(editorObject) {
    		preview = document.getElementById("preview-contents");
    
    		var converter = editorObject.getConverter();
    		converter.hooks.chain("preConversion", removeMath);
    		converter.hooks.chain("postConversion", replaceMath);
    	};
    
    	var afterRefreshCallback;
    	mathJax.onAsyncPreview = function(callback) {
    		afterRefreshCallback = callback;
    		UpdateMJ();
    	};
    
    	// From math.stackexchange.com...
    
    	//
    	//  The math is in blocks i through j, so
    	//    collect it into one block and clear the others.
    	//  Replace &, <, and > by named entities.
    	//  For IE, put <br> at the ends of comments since IE removes \n.
    	//  Clear the current math positions and store the index of the
    	//    math, then push the math string onto the storage array.
    	//
    	function processMath(i, j, unescape) {
    		var block = blocks.slice(i, j + 1).join("")
    			.replace(/&/g, "&amp;")
    			.replace(/</g, "&lt;")
    			.replace(/>/g, "&gt;");
    		for(HUB.Browser.isMSIE && (block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")); j > i;)
    			blocks[j] = "", j--;
    		blocks[i] = "@@" + math.length + "@@";
    		unescape && (block = unescape(block));
    		math.push(block);
    		start = end = last = null;
    	}
    
    	function removeMath(text) {
    		start = end = last = null;
    		math = [];
    		var unescape;
    		if(/`/.test(text)) {
    			text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function(text) {
    				return text.replace(/\$/g, "~D")
    			});
    			unescape = function(text) {
    				return text.replace(/~([TD])/g,
    					function(match, n) {
    						return {T: "~", D: "$"}[n]
    					})
    			};
    		} else {
    			unescape = function(text) {
    				return text
    			};
    		}
    		blocks = split(text.replace(/\r\n?/g, "\n"), splitDelimiter);
    		for(var i = 1, m = blocks.length; i < m; i += 2) {
    			var block = blocks[i];
    			if("@" === block.charAt(0)) {
    				//
    				//  Things that look like our math markers will get
    				//  stored and then retrieved along with the math.
    				//
    				blocks[i] = "@@" + math.length + "@@";
    				math.push(block)
    			} else if(start) {
    				// Ignore inline maths that are actually multiline (fixes #136)
    				if(end == inline && block.charAt(0) == '\n') {
    					if(last) {
    						i = last;
    						processMath(start, i, unescape);
    					}
    					start = end = last = null;
    					braces = 0;
    				}
    				//
    				//  If we are in math, look for the end delimiter,
    				//    but don't go past double line breaks, and
    				//    and balance braces within the math.
    				//
    				else if(block === end) {
    					if(braces) {
    						last = i
    					} else {
    						processMath(start, i, unescape)
    					}
    				} else {
    					if(block.match(/\n.*\n/)) {
    						if(last) {
    							i = last;
    							processMath(start, i, unescape);
    						}
    						start = end = last = null;
    						braces = 0;
    					} else {
    						if("{" === block) {
    							braces++
    						} else {
    							"}" === block && braces && braces--
    						}
    					}
    				}
    			} else {
    				if(block === inline || "$$" === block) {
    					start = i;
    					end = block;
    					braces = 0;
    				} else {
    					if("begin" === block.substr(1, 5)) {
    						start = i;
    						end = "\\end" + block.substr(6);
    						braces = 0;
    					}
    				}
    			}
    
    		}
    		last && processMath(start, last, unescape);
    		return unescape(blocks.join(""))
    	}
    
    	//
    	//  Put back the math strings that were saved,
    	//    and clear the math array (no need to keep it around).
    	//
    	function replaceMath(text) {
    		text = text.replace(/@@(\d+)@@/g, function(match, n) {
    			return math[n]
    		});
    		math = null;
    		return text
    	}
    
    	//
    	//  This is run to restart MathJax after it has finished
    	//    the previous run (that may have been canceled)
    	//
    	function RestartMJ() {
    		pending = false;
    		HUB.cancelTypeset = false;
    		HUB.Queue([
    			"Typeset",
    			HUB,
    			preview
    		]);
    		HUB.Queue(afterRefreshCallback); //benweet
    	}
    
    	//
    	//  When the preview changes, cancel MathJax and restart,
    	//    if we haven't done that already.
    	//
    	function UpdateMJ() {
    		if(!pending /*benweet (we need to call our afterRefreshCallback) && ready */) {
    			pending = true;
    			HUB.Cancel();
    			HUB.Queue(RestartMJ);
    		}
    	}
    
    	var ready = false, pending = false, preview = null, inline = "$", blocks, start, end, last, braces, math, HUB = MathJax.Hub;
    
    	//
    	//  Runs after initial typeset
    	//
    	HUB.Queue(function() {
    		ready = true;
    		HUB.processUpdateTime = 50;
    		HUB.Config({"HTML-CSS": {EqnChunk: 10, EqnChunkFactor: 1}, SVG: {EqnChunk: 10, EqnChunkFactor: 1}})
    	});
    
    
    	/*benweet
    	 Don't hash inline math $...$ (see https://github.com/benweet/stackedit/issues/136)
    	 var u = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i, r;
    	 */
    
    
    	//
    	//  The pattern for math delimiters and special symbols
    	//    needed for searching for math in the page.
    	//
    	var splitDelimiter = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
    	var split;
    
    	if(3 === "aba".split(/(b)/).length) {
    		split = function(text, delimiter) {
    			return text.split(delimiter)
    		};
    	} else {
    		split = function(text, delimiter) {
    			var b = [], c;
    			if(!delimiter.global) {
    				c = delimiter.toString();
    				var d = "";
    				c = c.replace(/^\/(.*)\/([im]*)$/, function(a, c, b) {
    					d = b;
    					return c
    				});
    				delimiter = RegExp(c, d + "g")
    			}
    			for(var e = delimiter.lastIndex = 0; c = delimiter.exec(text);) {
    				b.push(text.substring(e, c.index));
    				b.push.apply(b, c.slice(1));
    				e = c.index + c[0].length;
    			}
    			b.push(text.substring(e));
    			return b
    		};
    	}
    
    	(function() {
    		var HUB = MathJax.Hub;
    		if(!HUB.Cancel) {
    			HUB.cancelTypeset = !1;
    			HUB.Register.StartupHook("HTML-CSS Jax Config", function() {
    				var HTMLCSS = MathJax.OutputJax["HTML-CSS"], TRANSLATE = HTMLCSS.Translate;
    				HTMLCSS.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(HTMLCSS, script, state)
    				}})
    			});
    			HUB.Register.StartupHook("SVG Jax Config", function() {
    				var SVG = MathJax.OutputJax.SVG, TRANSLATE = SVG.Translate;
    				SVG.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(SVG,
    						script, state)
    				}})
    			});
    			HUB.Register.StartupHook("TeX Jax Config", function() {
    				var TEX = MathJax.InputJax.TeX, TRANSLATE = TEX.Translate;
    				TEX.Augment({Translate: function(script, state) {
    					if(HUB.cancelTypeset || state.cancelled)
    						throw Error("MathJax Canceled");
    					return TRANSLATE.call(TEX, script, state)
    				}})
    			});
    			var PROCESSERROR = HUB.processError;
    			HUB.processError = function(error, state, type) {
    				if("MathJax Canceled" !== error.message)
    					return PROCESSERROR.call(HUB, error, state, type);
    				MathJax.Message.Clear(0, 0);
    				state.jaxIDs = [];
    				state.jax = {};
    				state.scripts = [];
    				state.i = state.j = 0;
    				state.cancelled = true;
    				return null
    			};
    			HUB.Cancel = function() {
    				this.cancelTypeset = true
    			}
    		}
    	})();
    	/*jshint ignore:end */
    
    	return mathJax;
    });
    define('text!html/partialRenderingSettingsBlock.html',[],function () { return '<p>Renders modified sections only.</p>\n<blockquote>\n\t<p><b>Note:</b> Document sections are based on title elements (h1, h2...). Therefore if\n\tyour document does not contain any title, performance will not be increased.</p>\n</blockquote>';});
    
    define('extensions/partialRendering',[
    	"underscore",
    	"crel",
    	"extensions/markdownExtra",
    	"classes/Extension",
    	"text!html/partialRenderingSettingsBlock.html",
    ], function(_, crel, markdownExtra, Extension, partialRenderingSettingsBlockHTML) {
    
    	var partialRendering = new Extension("partialRendering", "Partial Rendering", true);
    	partialRendering.settingsBlock = partialRenderingSettingsBlockHTML;
    
    	var converter;
    	var doFootnotes = false;
    	var hasFootnotes = false;
    	var currentSectionList = [];
    
    	var sectionList = [];
    	var linkDefinition;
    	var sectionsToRemove = [];
    	var modifiedSections = [];
    	var insertBeforeSection;
    	var fileChanged = false;
    
    	function updateSectionList() {
    		var newSectionList = [];
    		var newLinkDefinition = '\n';
    		hasFootnotes = false;
    		_.each(currentSectionList, function(section) {
    			var text = '\n<div class="se-preview-section-delimiter"></div>\n\n' + section.text + '\n\n';
    
    			// Strip footnotes
    			if(doFootnotes) {
    				text = text.replace(/^```.*\n[\s\S]*?\n```|\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/gm, function(wholeMatch, footnote) {
    					if(footnote) {
    						hasFootnotes = true;
    						newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
    						return "";
    					}
    					return wholeMatch;
    				});
    			}
    
    			// Strip link definitions
    			text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
    				if(link) {
    					newLinkDefinition += wholeMatch.replace(/^\s*\n/gm, '') + '\n';
    					return "";
    				}
    				return wholeMatch;
    			});
    
    			// Add section to the newSectionList
    			newSectionList.push({
    				id: section.id,
    				text: text + '\n'
    			});
    		});
    
    		modifiedSections = [];
    		sectionsToRemove = [];
    		insertBeforeSection = undefined;
    
    		// Render everything if file or linkDefinition changed
    		if(fileChanged === true || linkDefinition != newLinkDefinition) {
    			fileChanged = false;
    			linkDefinition = newLinkDefinition;
    			sectionsToRemove = sectionList;
    			sectionList = newSectionList;
    			modifiedSections = newSectionList;
    			return;
    		}
    
    		// Find modified section starting from top
    		var leftIndex = sectionList.length;
    		_.some(sectionList, function(section, index) {
    			if(index >= newSectionList.length || section.text != newSectionList[index].text) {
    				leftIndex = index;
    				return true;
    			}
    		});
    
    		// Find modified section starting from bottom
    		var rightIndex = -sectionList.length;
    		_.some(sectionList.slice().reverse(), function(section, index) {
    			if(index >= newSectionList.length || section.text != newSectionList[newSectionList.length - index - 1].text) {
    				rightIndex = -index;
    				return true;
    			}
    		});
    
    		if(leftIndex - rightIndex > sectionList.length) {
    			// Prevent overlap
    			rightIndex = leftIndex - sectionList.length;
    		}
    
    		// Create an array composed of left unmodified, modified, right
    		// unmodified sections
    		var leftSections = sectionList.slice(0, leftIndex);
    		modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
    		var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
    		insertBeforeSection = _.first(rightSections);
    		sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
    		sectionList = leftSections.concat(modifiedSections).concat(rightSections);
    	}
    
    	var footnoteMap = {};
    	var footnoteFragment = document.createDocumentFragment();
    	// Store one footnote elt in the footnote map
    	function storeFootnote(footnoteElt) {
    		var id = footnoteElt.id.substring(3);
    		var oldFootnote = footnoteMap[id];
    		oldFootnote && footnoteFragment.removeChild(oldFootnote);
    		footnoteMap[id] = footnoteElt;
    		footnoteFragment.appendChild(footnoteElt);
    	}
    
    	var footnoteContainerElt;
    	var previewContentsElt;
    
    	function refreshSections() {
    
    		// Remove outdated sections
    		_.each(sectionsToRemove, function(section) {
    			var sectionElt = document.getElementById("wmd-preview-section-" + section.id);
    			previewContentsElt.removeChild(sectionElt);
    		});
    
    		var wmdPreviewElt = document.getElementById("wmd-preview");
    		var childNode = wmdPreviewElt.firstChild;
    
    		function createSectionElt(section) {
    			var sectionElt = crel('div', {
    				id: 'wmd-preview-section-' + section.id,
    				class: 'wmd-preview-section preview-content'
    			});
    			var isNextDelimiter = false;
    			while(childNode) {
    				var nextNode = childNode.nextSibling;
    				var isDelimiter = childNode.className == 'se-preview-section-delimiter';
    				if(isNextDelimiter === true && childNode.tagName == 'DIV' && isDelimiter) {
    					// Stop when encountered the next delimiter
    					break;
    				}
    				isNextDelimiter = true;
    				if(childNode.tagName == 'DIV' && childNode.className == 'footnotes') {
    					_.each(childNode.querySelectorAll("ol > li"), storeFootnote);
    				}
    				else {
    					isDelimiter || sectionElt.appendChild(childNode);
    				}
    				childNode = nextNode;
    			}
    			return sectionElt;
    		}
    
    		var newSectionEltList = document.createDocumentFragment();
    		_.each(modifiedSections, function(section) {
    			newSectionEltList.appendChild(createSectionElt(section));
    		});
    		wmdPreviewElt.innerHTML = '';
    		var insertBeforeElt = footnoteContainerElt;
    		if(insertBeforeSection !== undefined) {
    			insertBeforeElt = document.getElementById("wmd-preview-section-" + insertBeforeSection.id);
    		}
    		previewContentsElt.insertBefore(newSectionEltList, insertBeforeElt);
    
    		// Rewrite footnotes in the footer and update footnote numbers
    		footnoteContainerElt.innerHTML = '';
    		var usedFootnoteIds = [];
    		if(hasFootnotes === true) {
    			var footnoteElts = crel('ol');
    			_.each(previewContentsElt.querySelectorAll('a.footnote'), function(elt, index) {
    				elt.textContent = index + 1;
    				var id = elt.id.substring(6);
    				usedFootnoteIds.push(id);
    				var footnoteElt = footnoteMap[id];
    				footnoteElt && footnoteElts.appendChild(footnoteElt.cloneNode(true));
    			});
    			if(usedFootnoteIds.length > 0) {
    				// Append the whole footnotes at the end of the document
    				footnoteContainerElt.appendChild(crel('div', {
    					class: 'footnotes'
    				}, crel('hr'), footnoteElts));
    			}
    			// Keep used footnotes only in our map
    			Object.keys(footnoteMap).forEach(function(key) {
    				if(usedFootnoteIds.indexOf(key) === -1) {
    					footnoteFragment.removeChild(footnoteMap[key]);
    					delete footnoteMap[key];
    				}
    			});
    		}
    	}
    
    	partialRendering.onSectionsCreated = function(sectionListParam) {
    		currentSectionList = sectionListParam;
    	};
    
    	partialRendering.onPagedownConfigure = function(editor) {
    		converter = editor.getConverter();
    		converter.hooks.chain("preConversion", function() {
    			updateSectionList();
    			var result = _.map(modifiedSections, function(section) {
    				return section.text;
    			});
    			result.push(linkDefinition + "\n\n");
    			return result.join("");
    		});
    		editor.hooks.chain("onPreviewRefresh", function() {
    			refreshSections();
    		});
    	};
    
    	partialRendering.onInit = function() {
    		if(markdownExtra.enabled) {
    			if(_.some(markdownExtra.config.extensions, function(extension) {
    				return extension == "footnotes";
    			})) {
    				doFootnotes = true;
    			}
    		}
    	};
    
    	partialRendering.onReady = function() {
    		footnoteContainerElt = crel('div', {
    			id: 'wmd-preview-section-footnotes',
    			class: 'preview-content'
    		});
    		previewContentsElt = document.getElementById("preview-contents");
    		previewContentsElt.appendChild(footnoteContainerElt);
    	};
    
    	partialRendering.onFileSelected = function() {
    		fileChanged = true;
    	};
    
    	return partialRendering;
    });
    
    define('extensions/markdownSectionParser',[
        "underscore",
        "extensions/markdownExtra",
        "extensions/mathJax",
        "extensions/partialRendering",
        "classes/Extension",
        "crel",
    ], function(_, markdownExtra, mathJax, partialRendering, Extension, crel) {
    
        var markdownSectionParser = new Extension("markdownSectionParser", "Markdown section parser");
    
        var eventMgr;
        markdownSectionParser.onEventMgrCreated = function(eventMgrParameter) {
            eventMgr = eventMgrParameter;
        };
    
        var sectionList = [];
        var previewContentsElt;
    
        // Regexp to look for section delimiters
        var regexp = '^.+[ \\t]*\\n=+[ \\t]*\\n+|^.+[ \\t]*\\n-+[ \\t]*\\n+|^\\#{1,6}[ \\t]*.+?[ \\t]*\\#*\\n+'; // Title delimiters
        markdownSectionParser.onPagedownConfigure = function(editor) {
            if(markdownExtra.enabled) {
                if(_.some(markdownExtra.config.extensions, function(extension) {
                    return extension == "fenced_code_gfm";
                })) {
                    regexp = '^```[^`\\n]*\\n[\\s\\S]*?\\n```|' + regexp; // Fenced block delimiters
                }
            }
            if(mathJax.enabled) {
                // Math delimiter has to follow 1 empty line to be considered as a section delimiter
                regexp = '^[ \\t]*\\n\\$\\$[\\s\\S]*?\\$\\$|' + regexp; // $$ math block delimiters
                regexp = '^[ \\t]*\\n\\\\\\\\[[\\s\\S]*?\\\\\\\\]|' + regexp; // \\[ \\] math block delimiters
                regexp = '^[ \\t]*\\n\\\\?\\\\begin\\{[a-z]*\\*?\\}[\\s\\S]*?\\\\end\\{[a-z]*\\*?\\}|' + regexp; // \\begin{...} \\end{...} math block delimiters
            }
            regexp = new RegExp(regexp, 'gm');
    
            var converter = editor.getConverter();
            if(!partialRendering.enabled) {
                converter.hooks.chain("preConversion", function() {
                    return _.reduce(sectionList, function(result, section) {
                        return result + '\n<div class="se-preview-section-delimiter"></div>\n\n' + section.text + '\n\n';
                    }, '');
                });
    
                editor.hooks.chain("onPreviewRefresh", function() {
                    var wmdPreviewElt = document.getElementById("wmd-preview");
                    var childNode = wmdPreviewElt.firstChild;
                    function createSectionElt() {
                        var sectionElt = crel('div', {
                            class: 'wmd-preview-section preview-content'
                        });
                        var isNextDelimiter = false;
                        while (childNode) {
                            var nextNode = childNode.nextSibling;
                            var isDelimiter = childNode.className == 'se-preview-section-delimiter';
                            if(isNextDelimiter === true && childNode.tagName == 'DIV' && isDelimiter) {
                                // Stop when encountered the next delimiter
                                break;
                            }
                            isNextDelimiter = true;
                            isDelimiter || sectionElt.appendChild(childNode);
                            childNode = nextNode;
                        }
                        return sectionElt;
                    }
    
                    var newSectionEltList = document.createDocumentFragment();
                    sectionList.forEach(function(section) {
                        newSectionEltList.appendChild(createSectionElt(section));
                    });
                    previewContentsElt.innerHTML = '';
                    previewContentsElt.appendChild(wmdPreviewElt);
                    previewContentsElt.appendChild(newSectionEltList);
                });
            }
        };
    
        markdownSectionParser.onReady = function() {
            previewContentsElt = document.getElementById("preview-contents");
        };
    
        var fileDesc;
        markdownSectionParser.onFileSelected = function(fileDescParam) {
            fileDesc = fileDescParam;
        };
    
        var sectionCounter = 0;
        // 当内容改变时, 触发之
        // content全部内容
        function parseFileContent(fileDescParam, content) {
            if(fileDescParam !== fileDesc) {
                return;
            }
            var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
            var text = content.substring(frontMatter.length);
            var tmpText = text + "\n\n";
            function addSection(startOffset, endOffset) {
                var sectionText = tmpText.substring(offset, endOffset);
                sectionList.push({
                    id: ++sectionCounter,
                    text: sectionText,
                    textWithFrontMatter: frontMatter + sectionText
                });
                frontMatter = '';
            }
            sectionList = [];
            var offset = 0;
            // Look for delimiters
            tmpText.replace(regexp, function(match, matchOffset) {
                // Create a new section with the text preceding the delimiter
                addSection(offset, matchOffset);
                offset = matchOffset;
            });
            // Last section
            addSection(offset, text.length);
            // 触发事件
            eventMgr.onSectionsCreated(sectionList);
        }
    
        markdownSectionParser.onFileOpen = parseFileContent;
        markdownSectionParser.onContentChanged = parseFileContent;
    
        return markdownSectionParser;
    });
    
    define('extensions/workingIndicator',[
        // "jquery",
        "underscore",
        "crel",
        "classes/Extension"
    ], function (_, crel, Extension) {
    
        var workingIndicator = new Extension("workingIndicator", "Working Indicator");
    
        var keyframeTemlate = [
            '@<%= prefix %>keyframes <%= name %> {',
            '    0% { opacity:<%= z %>; }',
            '    <%= start %>.01% { opacity:<%= alpha %>; }',
            '    <%= start %>.02% { opacity:1; }',
            '    <%= ((start + trail) % 100) %>.01% { opacity:<%= alpha %>; }',
            '    100% { opacity:<%= z %>; }',
            '}'
        ].join('\n');
    
        var $bodyElt;
        var $workingIndicatorElt;
        workingIndicator.onAsyncRunning = function (isRunning) {
            $bodyElt.toggleClass("working", isRunning);
            $workingIndicatorElt.toggleClass("hide", !isRunning);
        };
    
        workingIndicator.onReady = function () {
            var styleContent = '';
    
            function addKeyframe(params) {
                params.z = Math.max(1 - (1-params.alpha) / params.trail * (100-params.start), params.alpha);
                styleContent += _.template(keyframeTemlate, _.extend({
                    prefix: ''
                }, params));
                styleContent += _.template(keyframeTemlate, _.extend({
                    prefix: '-webkit-'
                }, params));
            }
            $bodyElt = $(document.body);
            $workingIndicatorElt = $('<div class="hide">');
            $('.working-indicator').append($workingIndicatorElt);
            for (var i = 0; i < 3; i++) {
                var name = 'working-indicator-bar' + i;
                addKeyframe({
                    name: name,
                    alpha: 0.25,
                    start: 20 * i,
                    trail: 50
                });
                var animation = name + ' 0.7s linear infinite';
                $workingIndicatorElt.append($('<div class="bar">').css({
                    'animation': animation,
                    '-webkit-animation': animation,
                }));
            }
            var styleElt = crel('style', {
                type : 'text/css'
            });
            document.head.appendChild(styleElt);
            styleElt.innerHTML = styleContent;
        };
    
        return workingIndicator;
    });
    
    /**
     * jGrowl 1.2.13
     *
     * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
     * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
     *
     * Written by Stan Lemon <stosh1985@gmail.com>
     * Last updated: 2013.02.22
     *
     * jGrowl is a jQuery plugin implementing unobtrusive userland notifications.  These
     * notifications function similarly to the Growl Framework available for
     * Mac OS X (http://growl.info).
     *
     * To Do:
     * - Move library settings to containers and allow them to be changed per container
     *
     * Changes in 1.2.13
     * - Fixed clearing interval when the container shuts down
     *
     * Changes in 1.2.12
     * - Added compressed versions using UglifyJS and Sqwish
     * - Improved README with configuration options explanation
     * - Added a source map
     *
     * Changes in 1.2.11
     * - Fix artifacts left behind by the shutdown method and text-cleanup
     *
     * Changes in 1.2.10
     * - Fix beforeClose to be called in click event
     *
     * Changes in 1.2.9
     * - Fixed BC break in jQuery 2.0 beta
     *
     * Changes in 1.2.8
     * - Fixes for jQuery 1.9 and the MSIE6 check, note that with jQuery 2.0 support
     *   jGrowl intends to drop support for IE6 altogether
     *
     * Changes in 1.2.6
     * - Fixed js error when a notification is opening and closing at the same time
     *
     * Changes in 1.2.5
     * - Changed wrapper jGrowl's options usage to "o" instead of $.jGrowl.defaults
     * - Added themeState option to control 'highlight' or 'error' for jQuery UI
     * - Ammended some CSS to provide default positioning for nested usage.
     * - Changed some CSS to be prefixed with jGrowl- to prevent namespacing issues
     * - Added two new options - openDuration and closeDuration to allow
     *   better control of notification open and close speeds, respectively
     *   Patch contributed by Jesse Vincet.
     * - Added afterOpen callback.  Patch contributed by Russel Branca.
     *
     * Changes in 1.2.4
     * - Fixed IE bug with the close-all button
     * - Fixed IE bug with the filter CSS attribute (special thanks to gotwic)
     * - Update IE opacity CSS
     * - Changed font sizes to use "em", and only set the base style
     *
     * Changes in 1.2.3
     * - The callbacks no longer use the container as context, instead they use the actual notification
     * - The callbacks now receive the container as a parameter after the options parameter
     * - beforeOpen and beforeClose now check the return value, if it's false - the notification does
     *   not continue.  The open callback will also halt execution if it returns false.
     * - Fixed bug where containers would get confused
     * - Expanded the pause functionality to pause an entire container.
     *
     * Changes in 1.2.2
     * - Notification can now be theme rolled for jQuery UI, special thanks to Jeff Chan!
     *
     * Changes in 1.2.1
     * - Fixed instance where the interval would fire the close method multiple times.
     * - Added CSS to hide from print media
     * - Fixed issue with closer button when div { position: relative } is set
     * - Fixed leaking issue with multiple containers.  Special thanks to Matthew Hanlon!
     *
     * Changes in 1.2.0
     * - Added message pooling to limit the number of messages appearing at a given time.
     * - Closing a notification is now bound to the notification object and triggered by the close button.
     *
     * Changes in 1.1.2
     * - Added iPhone styled example
     * - Fixed possible IE7 bug when determining if the ie6 class shoudl be applied.
     * - Added template for the close button, so that it's content could be customized.
     *
     * Changes in 1.1.1
     * - Fixed CSS styling bug for ie6 caused by a mispelling
     * - Changes height restriction on default notifications to min-height
     * - Added skinned examples using a variety of images
     * - Added the ability to customize the content of the [close all] box
     * - Added jTweet, an example of using jGrowl + Twitter
     *
     * Changes in 1.1.0
     * - Multiple container and instances.
     * - Standard $.jGrowl() now wraps $.fn.jGrowl() by first establishing a generic jGrowl container.
     * - Instance methods of a jGrowl container can be called by $.fn.jGrowl(methodName)
     * - Added glue preferenced, which allows notifications to be inserted before or after nodes in the container
     * - Added new log callback which is called before anything is done for the notification
     * - Corner's attribute are now applied on an individual notification basis.
     *
     * Changes in 1.0.4
     * - Various CSS fixes so that jGrowl renders correctly in IE6.
     *
     * Changes in 1.0.3
     * - Fixed bug with options persisting across notifications
     * - Fixed theme application bug
     * - Simplified some selectors and manipulations.
     * - Added beforeOpen and beforeClose callbacks
     * - Reorganized some lines of code to be more readable
     * - Removed unnecessary this.defaults context
     * - If corners plugin is present, it's now customizable.
     * - Customizable open animation.
     * - Customizable close animation.
     * - Customizable animation easing.
     * - Added customizable positioning (top-left, top-right, bottom-left, bottom-right, center)
     *
     * Changes in 1.0.2
     * - All CSS styling is now external.
     * - Added a theme parameter which specifies a secondary class for styling, such
     *   that notifications can be customized in appearance on a per message basis.
     * - Notification life span is now customizable on a per message basis.
     * - Added the ability to disable the global closer, enabled by default.
     * - Added callbacks for when a notification is opened or closed.
     * - Added callback for the global closer.
     * - Customizable animation speed.
     * - jGrowl now set itself up and tears itself down.
     *
     * Changes in 1.0.1:
     * - Removed dependency on metadata plugin in favor of .data()
     * - Namespaced all events
     */
    (function($) {
    	/** Compatibility holdover for 1.9 to check IE6 **/
    	var $ie6 = (function(){
    		return false === $.support.boxModel && $.support.objectAll && $.support.leadingWhitespace;
    	})();
    
    	/** jGrowl Wrapper - Establish a base jGrowl Container for compatibility with older releases. **/
    	$.jGrowl = function( m , o ) {
    		// To maintain compatibility with older version that only supported one instance we'll create the base container.
    		if ( $('#jGrowl').size() == 0 )
    			$('<div id="jGrowl"></div>').addClass( (o && o.position) ? o.position : $.jGrowl.defaults.position ).appendTo('body');
    
    		// Create a notification on the container.
    		$('#jGrowl').jGrowl(m,o);
    	};
    
    
    	/** Raise jGrowl Notification on a jGrowl Container **/
    	$.fn.jGrowl = function( m , o ) {
    		if ( $.isFunction(this.each) ) {
    			var args = arguments;
    
    			return this.each(function() {
    				/** Create a jGrowl Instance on the Container if it does not exist **/
    				if ( $(this).data('jGrowl.instance') == undefined ) {
    					$(this).data('jGrowl.instance', $.extend( new $.fn.jGrowl(), { notifications: [], element: null, interval: null } ));
    					$(this).data('jGrowl.instance').startup( this );
    				}
    
    				/** Optionally call jGrowl instance methods, or just raise a normal notification **/
    				if ( $.isFunction($(this).data('jGrowl.instance')[m]) ) {
    					$(this).data('jGrowl.instance')[m].apply( $(this).data('jGrowl.instance') , $.makeArray(args).slice(1) );
    				} else {
    					$(this).data('jGrowl.instance').create( m , o );
    				}
    			});
    		};
    	};
    
    	$.extend( $.fn.jGrowl.prototype , {
    
    		/** Default JGrowl Settings **/
    		defaults: {
    			pool:				0,
    			header:				'',
    			group:				'',
    			sticky:				false,
    			position: 			'top-right',
    			glue:				'after',
    			theme:				'default',
    			themeState:			'highlight',
    			corners:			'10px',
    			check:				250,
    			life:				3000,
    			closeDuration: 		'normal',
    			openDuration: 		'normal',
    			easing: 			'swing',
    			closer: 			true,
    			closeTemplate: 		'&times;',
    			closerTemplate: 	'<div>[ close all ]</div>',
    			log:				function() {},
    			beforeOpen:			function() {},
    			afterOpen:			function() {},
    			open:				function() {},
    			beforeClose: 		function() {},
    			close:				function() {},
    			animateOpen: 		{
    				opacity:	 'show'
    			},
    			animateClose: 		{
    				opacity:	 'hide'
    			}
    		},
    
    		notifications: [],
    
    		/** jGrowl Container Node **/
    		element:	 null,
    
    		/** Interval Function **/
    		interval:   null,
    
    		/** Create a Notification **/
    		create:	 function( message , o ) {
    			var o = $.extend({}, this.defaults, o);
    
    			/* To keep backward compatibility with 1.24 and earlier, honor 'speed' if the user has set it */
    			if (typeof o.speed !== 'undefined') {
    				o.openDuration = o.speed;
    				o.closeDuration = o.speed;
    			}
    
    			this.notifications.push({ message: message , options: o });
    
    			o.log.apply( this.element , [this.element,message,o] );
    		},
    
    		render:		 function( notification ) {
    			var self = this;
    			var message = notification.message;
    			var o = notification.options;
    
    			// Support for jQuery theme-states, if this is not used it displays a widget header
    			o.themeState = (o.themeState == '') ? '' : 'ui-state-' + o.themeState;
    
    			var notification = $('<div/>')
    				.addClass('jGrowl-notification ' + o.themeState + ' ui-corner-all' + ((o.group != undefined && o.group != '') ? ' ' + o.group : ''))
    				.append($('<div/>').addClass('jGrowl-close').html(o.closeTemplate))
    				.append($('<div/>').addClass('jGrowl-header').html(o.header))
    				.append($('<div/>').addClass('jGrowl-message').html(message))
    				.data("jGrowl", o).addClass(o.theme).children('div.jGrowl-close').bind("click.jGrowl", function() {
    					$(this).parent().trigger('jGrowl.beforeClose');
    				})
    				.parent();
    
    
    			/** Notification Actions **/
    			$(notification).bind("mouseover.jGrowl", function() {
    				$('div.jGrowl-notification', self.element).data("jGrowl.pause", true);
    			}).bind("mouseout.jGrowl", function() {
    				$('div.jGrowl-notification', self.element).data("jGrowl.pause", false);
    			}).bind('jGrowl.beforeOpen', function() {
    				if ( o.beforeOpen.apply( notification , [notification,message,o,self.element] ) !== false ) {
    					$(this).trigger('jGrowl.open');
    				}
    			}).bind('jGrowl.open', function() {
    				if ( o.open.apply( notification , [notification,message,o,self.element] ) !== false ) {
    					if ( o.glue == 'after' ) {
    						$('div.jGrowl-notification:last', self.element).after(notification);
    					} else {
    						$('div.jGrowl-notification:first', self.element).before(notification);
    					}
    
    					$(this).animate(o.animateOpen, o.openDuration, o.easing, function() {
    						// Fixes some anti-aliasing issues with IE filters.
    						if ($.support.opacity === false)
    							this.style.removeAttribute('filter');
    
    						if ( $(this).data("jGrowl") !== null ) // Happens when a notification is closing before it's open.
    							$(this).data("jGrowl").created = new Date();
    
    						$(this).trigger('jGrowl.afterOpen');
    					});
    				}
    			}).bind('jGrowl.afterOpen', function() {
    				o.afterOpen.apply( notification , [notification,message,o,self.element] );
    			}).bind('jGrowl.beforeClose', function() {
    				if ( o.beforeClose.apply( notification , [notification,message,o,self.element] ) !== false )
    					$(this).trigger('jGrowl.close');
    			}).bind('jGrowl.close', function() {
    				// Pause the notification, lest during the course of animation another close event gets called.
    				$(this).data('jGrowl.pause', true);
    				$(this).animate(o.animateClose, o.closeDuration, o.easing, function() {
    					if ( $.isFunction(o.close) ) {
    						if ( o.close.apply( notification , [notification,message,o,self.element] ) !== false )
    							$(this).remove();
    					} else {
    						$(this).remove();
    					}
    				});
    			}).trigger('jGrowl.beforeOpen');
    
    			/** Optional Corners Plugin **/
    			if ( o.corners != '' && $.fn.corner != undefined ) $(notification).corner( o.corners );
    
    			/** Add a Global Closer if more than one notification exists **/
    			if ( $('div.jGrowl-notification:parent', self.element).size() > 1 &&
    				 $('div.jGrowl-closer', self.element).size() == 0 && this.defaults.closer !== false ) {
    				$(this.defaults.closerTemplate).addClass('jGrowl-closer ' + this.defaults.themeState + ' ui-corner-all').addClass(this.defaults.theme)
    					.appendTo(self.element).animate(this.defaults.animateOpen, this.defaults.speed, this.defaults.easing)
    					.bind("click.jGrowl", function() {
    						$(this).siblings().trigger("jGrowl.beforeClose");
    
    						if ( $.isFunction( self.defaults.closer ) ) {
    							self.defaults.closer.apply( $(this).parent()[0] , [$(this).parent()[0]] );
    						}
    					});
    			};
    		},
    
    		/** Update the jGrowl Container, removing old jGrowl notifications **/
    		update:	 function() {
    			$(this.element).find('div.jGrowl-notification:parent').each( function() {
    				if ( $(this).data("jGrowl") != undefined && $(this).data("jGrowl").created !== undefined &&
    					 ($(this).data("jGrowl").created.getTime() + parseInt($(this).data("jGrowl").life))  < (new Date()).getTime() &&
    					 $(this).data("jGrowl").sticky !== true &&
    					 ($(this).data("jGrowl.pause") == undefined || $(this).data("jGrowl.pause") !== true) ) {
    
    					// Pause the notification, lest during the course of animation another close event gets called.
    					$(this).trigger('jGrowl.beforeClose');
    				}
    			});
    
    			if ( this.notifications.length > 0 &&
    				 (this.defaults.pool == 0 || $(this.element).find('div.jGrowl-notification:parent').size() < this.defaults.pool) )
    				this.render( this.notifications.shift() );
    
    			if ( $(this.element).find('div.jGrowl-notification:parent').size() < 2 ) {
    				$(this.element).find('div.jGrowl-closer').animate(this.defaults.animateClose, this.defaults.speed, this.defaults.easing, function() {
    					$(this).remove();
    				});
    			}
    		},
    
    		/** Setup the jGrowl Notification Container **/
    		startup:	function(e) {
    			this.element = $(e).addClass('jGrowl').append('<div class="jGrowl-notification"></div>');
    			this.interval = setInterval( function() {
    				$(e).data('jGrowl.instance').update();
    			}, parseInt(this.defaults.check));
    
    			if ($ie6) {
    				$(this.element).addClass('ie6');
    			}
    		},
    
    		/** Shutdown jGrowl, removing it and clearing the interval **/
    		shutdown:   function() {
    			$(this.element).removeClass('jGrowl')
    				.find('div.jGrowl-notification').trigger('jGrowl.close')
    				.parent().empty()
    
    			clearInterval(this.interval);
    		},
    
    		close:	 function() {
    			$(this.element).find('div.jGrowl-notification').each(function(){
    				$(this).trigger('jGrowl.beforeClose');
    			});
    		}
    	});
    
    	/** Reference the Defaults Object for compatibility with older versions of jGrowl **/
    	$.jGrowl.defaults = $.fn.jGrowl.prototype.defaults;
    
    })(jQuery);
    define("jgrowl", (function (global) {
        return function () {
            var ret, fn;
            return ret || global.jQuery.jGrowl;
        };
    }(this)));
    
    define('text!html/notificationsSettingsBlock.html',[],function () { return '<p>Shows notification messages in the bottom-right corner of the\n\tscreen.</p>\n<div class="form-horizontal">\n\t<div class="form-group">\n\t\t<label class="col-sm-4 control-label" for="input-notifications-timeout">Timeout</label>\n\t\t<div class="col-sm-7 form-inline">\n\t\t\t<input type="text" id="input-notifications-timeout"\n\t\t\t\tclass="col-sm-5 form-control"> <span class="help-inline">ms</span>\n\t\t</div>\n\t</div>\n</div>';});
    
    define('extensions/notifications',[
        // "jquery",
        "underscore",
        "utils",
        "logger",
        "classes/Extension",
        "jgrowl",
        "text!html/notificationsSettingsBlock.html",
    ], function(_, utils, logger, Extension, jGrowl, notificationsSettingsBlockHTML) {
    
        var notifications = new Extension("notifications", "Notifications");
        notifications.settingsBlock = notificationsSettingsBlockHTML;
        notifications.defaultConfig = {
            timeout: 8000
        };
    
        notifications.onLoadSettings = function() {
            utils.setInputValue("#input-notifications-timeout", notifications.config.timeout);
        };
    
        notifications.onSaveSettings = function(newConfig, event) {
            newConfig.timeout = utils.getInputIntValue("#input-notifications-timeout", event, 1, 60000);
        };
    
        var isInit = false;
        function init() {
            if(isInit === false) {
                // jGrowl configuration
                jGrowl.defaults.life = notifications.config.timeout;
                jGrowl.defaults.closer = false;
                jGrowl.defaults.closeTemplate = '';
                jGrowl.defaults.position = 'bottom-right';
                isInit = true;
            }
        }
    
        function showMessage(message, iconClass, options) {
            logger.info(message);
            init();
            if(!message) {
                return;
            }
            var endOfMsg = message.indexOf("|");
            if(endOfMsg !== -1) {
                message = message.substring(0, endOfMsg);
                if(!message) {
                    return;
                }
            }
            options = options || {};
            iconClass = iconClass || "icon-info-circled";
            jGrowl("<i class='icon-white " + iconClass + "'></i> " + _.escape(message).replace(/\n/g, '<br/>'), options);
        }
    
        var isReady = false;
        var $offlineStatusElt;
        var $extensionButtonsElt;
        notifications.onReady = function() {
            isReady = true;
            $offlineStatusElt = $('.navbar .offline-status');
            $extensionButtonsElt = $('.navbar .extension-buttons');
            updateOnlineStatus();
        };
    
        notifications.onMessage = function(message) {
            showMessage(message);
        };
    
        notifications.onError = function(error) {
            logger.error(error);
            if(_.isString(error)) {
                showMessage(error, "icon-attention");
            }
            else if(_.isObject(error)) {
                showMessage(error.message, "icon-attention");
            }
        };
    
        var isOffline = false;
        function updateOnlineStatus() {
            if(isReady === false) {
                return;
            }
            $offlineStatusElt.toggleClass('hide', !isOffline);
            $extensionButtonsElt.toggleClass('hide', isOffline);
        }
        notifications.onOfflineChanged = function(isOfflineParam) {
            isOffline = isOfflineParam;
            updateOnlineStatus();
            if(isOffline === true) {
                showMessage("You are offline.", "icon-attention-circled msg-offline");
            }
            else {
                showMessage("You are back online!", "icon-signal");
            }
        };
    
        notifications.onSyncImportSuccess = function(fileDescList, provider) {
            var titles = _.map(fileDescList, function(fileDesc) {
                return fileDesc.title;
            }).join(", ");
            showMessage(titles + ' imported successfully from ' + provider.providerName + '.');
        };
    
        notifications.onSyncExportSuccess = function(fileDesc, syncAttributes) {
            showMessage('"' + fileDesc.title + '" will now be synchronized on ' + syncAttributes.provider.providerName + '.');
        };
    
        notifications.onSyncRemoved = function(fileDesc, syncAttributes) {
            showMessage(syncAttributes.provider.providerName + " synchronized location has been removed.");
        };
    
        notifications.onPublishSuccess = function(fileDesc) {
            showMessage('"' + fileDesc.title + '" successfully published.');
        };
    
        notifications.onNewPublishSuccess = function(fileDesc, publishAttributes) {
            showMessage('"' + fileDesc.title + '" is now published on ' + publishAttributes.provider.providerName + '.');
        };
    
        notifications.onPublishRemoved = function(fileDesc, publishAttributes) {
            showMessage(publishAttributes.provider.providerName + " publish location has been removed.");
        };
    
        return notifications;
    });
    define('text!html/umlDiagramsSettingsBlock.html',[],function () { return '<p>Creates UML diagrams from plain text description.</p>\n\n<div class="form-horizontal">\n    <div class="form-group">\n        <label class="col-sm-4 control-label" for="textarea-umldiagram-flowchart-options">Flow charts options (JSON)\n        </label>\n        <div class="col-sm-7">\n            <textarea id="textarea-umldiagram-flowchart-options" class="form-control"></textarea>\n        </div>\n    </div>\n</div>\n<br>\n<p>Sequence diagrams:</p>\n<pre><div class="help-block pull-right"><a target="_blank" href="http://bramp.github.io/js-sequence-diagrams/">More info</a></div><code>```sequence\nAlice->Bob: Hello Bob, how are you?\nBob-->Alice: I am good thanks!\n```</code>\n</pre>\n\n<p>Flow charts:</p>\n<pre><div class="help-block pull-right"><a target="_blank" href="http://adrai.github.io/flowchart.js/">More info</a></div><code>```flow\nst=>start: Start\ne=>end\nop=>operation: My Operation\ncond=>condition: Yes or No?\nst->op->cond\ncond(yes)->e\ncond(no)->op\n```</code>\n</pre>\n<blockquote>\n    <p><b>Note:</b> Markdown Extra extension has to be enabled with GFM fenced code blocks option.</p>\n</blockquote>\n';});
    
    // ┌────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël 2.1.1 - JavaScript Vector Library                          │ \\
    // ├────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
    // │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
    // ├────────────────────────────────────────────────────────────────────┤ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
    // └────────────────────────────────────────────────────────────────────┘ \\
    // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
    // 
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    // 
    // http://www.apache.org/licenses/LICENSE-2.0
    // 
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    // ┌────────────────────────────────────────────────────────────┐ \\
    // │ Eve 0.4.2 - JavaScript Events Library                      │ \\
    // ├────────────────────────────────────────────────────────────┤ \\
    // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
    // └────────────────────────────────────────────────────────────┘ \\
    
    (function (glob) {
        var version = "0.4.2",
            has = "hasOwnProperty",
            separator = /[\.\/]/,
            wildcard = "*",
            fun = function () {},
            numsort = function (a, b) {
                return a - b;
            },
            current_event,
            stop,
            events = {n: {}},
        /*\
         * eve
         [ method ]
    
         * Fires event with given `name`, given scope and other parameters.
    
         > Arguments
    
         - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
         - scope (object) context for the event handlers
         - varargs (...) the rest of arguments will be sent to event handlers
    
         = (object) array of returned values from the listeners
        \*/
            eve = function (name, scope) {
    			name = String(name);
                var e = events,
                    oldstop = stop,
                    args = Array.prototype.slice.call(arguments, 2),
                    listeners = eve.listeners(name),
                    z = 0,
                    f = false,
                    l,
                    indexed = [],
                    queue = {},
                    out = [],
                    ce = current_event,
                    errors = [];
                current_event = name;
                stop = 0;
                for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
                    indexed.push(listeners[i].zIndex);
                    if (listeners[i].zIndex < 0) {
                        queue[listeners[i].zIndex] = listeners[i];
                    }
                }
                indexed.sort(numsort);
                while (indexed[z] < 0) {
                    l = queue[indexed[z++]];
                    out.push(l.apply(scope, args));
                    if (stop) {
                        stop = oldstop;
                        return out;
                    }
                }
                for (i = 0; i < ii; i++) {
                    l = listeners[i];
                    if ("zIndex" in l) {
                        if (l.zIndex == indexed[z]) {
                            out.push(l.apply(scope, args));
                            if (stop) {
                                break;
                            }
                            do {
                                z++;
                                l = queue[indexed[z]];
                                l && out.push(l.apply(scope, args));
                                if (stop) {
                                    break;
                                }
                            } while (l)
                        } else {
                            queue[l.zIndex] = l;
                        }
                    } else {
                        out.push(l.apply(scope, args));
                        if (stop) {
                            break;
                        }
                    }
                }
                stop = oldstop;
                current_event = ce;
                return out.length ? out : null;
            };
    		// Undocumented. Debug only.
    		eve._events = events;
        /*\
         * eve.listeners
         [ method ]
    
         * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
    
         > Arguments
    
         - name (string) name of the event, dot (`.`) or slash (`/`) separated
    
         = (array) array of event handlers
        \*/
        eve.listeners = function (name) {
            var names = name.split(separator),
                e = events,
                item,
                items,
                k,
                i,
                ii,
                j,
                jj,
                nes,
                es = [e],
                out = [];
            for (i = 0, ii = names.length; i < ii; i++) {
                nes = [];
                for (j = 0, jj = es.length; j < jj; j++) {
                    e = es[j].n;
                    items = [e[names[i]], e[wildcard]];
                    k = 2;
                    while (k--) {
                        item = items[k];
                        if (item) {
                            nes.push(item);
                            out = out.concat(item.f || []);
                        }
                    }
                }
                es = nes;
            }
            return out;
        };
        
        /*\
         * eve.on
         [ method ]
         **
         * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
         | eve.on("*.under.*", f);
         | eve("mouse.under.floor"); // triggers f
         * Use @eve to trigger the listener.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
         **
         = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
         > Example:
         | eve.on("mouse", eatIt)(2);
         | eve.on("mouse", scream);
         | eve.on("mouse", catchIt)(1);
         * This will ensure that `catchIt()` function will be called before `eatIt()`.
    	 *
         * If you want to put your handler before non-indexed handlers, specify a negative value.
         * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
        \*/
        eve.on = function (name, f) {
    		name = String(name);
    		if (typeof f != "function") {
    			return function () {};
    		}
            var names = name.split(separator),
                e = events;
            for (var i = 0, ii = names.length; i < ii; i++) {
                e = e.n;
                e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
            }
            e.f = e.f || [];
            for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
                return fun;
            }
            e.f.push(f);
            return function (zIndex) {
                if (+zIndex == +zIndex) {
                    f.zIndex = +zIndex;
                }
            };
        };
        /*\
         * eve.f
         [ method ]
         **
         * Returns function that will fire given event with optional arguments.
    	 * Arguments that will be passed to the result function will be also
    	 * concated to the list of final arguments.
     	 | el.onclick = eve.f("click", 1, 2);
     	 | eve.on("click", function (a, b, c) {
     	 |     console.log(a, b, c); // 1, 2, [event object]
     	 | });
         > Arguments
    	 - event (string) event name
    	 - varargs (…) and any other arguments
    	 = (function) possible event handler function
        \*/
    	eve.f = function (event) {
    		var attrs = [].slice.call(arguments, 1);
    		return function () {
    			eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
    		};
    	};
        /*\
         * eve.stop
         [ method ]
         **
         * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
        \*/
        eve.stop = function () {
            stop = 1;
        };
        /*\
         * eve.nt
         [ method ]
         **
         * Could be used inside event handler to figure out actual name of the event.
         **
         > Arguments
         **
         - subname (string) #optional subname of the event
         **
         = (string) name of the event, if `subname` is not specified
         * or
         = (boolean) `true`, if current event’s name contains `subname`
        \*/
        eve.nt = function (subname) {
            if (subname) {
                return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
            }
            return current_event;
        };
        /*\
         * eve.nts
         [ method ]
         **
         * Could be used inside event handler to figure out actual name of the event.
         **
         **
         = (array) names of the event
        \*/
        eve.nts = function () {
            return current_event.split(separator);
        };
        /*\
         * eve.off
         [ method ]
         **
         * Removes given function from the list of event listeners assigned to given name.
    	 * If no arguments specified all the events will be cleared.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
        \*/
        /*\
         * eve.unbind
         [ method ]
         **
         * See @eve.off
        \*/
        eve.off = eve.unbind = function (name, f) {
    		if (!name) {
    		    eve._events = events = {n: {}};
    			return;
    		}
            var names = name.split(separator),
                e,
                key,
                splice,
                i, ii, j, jj,
                cur = [events];
            for (i = 0, ii = names.length; i < ii; i++) {
                for (j = 0; j < cur.length; j += splice.length - 2) {
                    splice = [j, 1];
                    e = cur[j].n;
                    if (names[i] != wildcard) {
                        if (e[names[i]]) {
                            splice.push(e[names[i]]);
                        }
                    } else {
                        for (key in e) if (e[has](key)) {
                            splice.push(e[key]);
                        }
                    }
                    cur.splice.apply(cur, splice);
                }
            }
            for (i = 0, ii = cur.length; i < ii; i++) {
                e = cur[i];
                while (e.n) {
                    if (f) {
                        if (e.f) {
                            for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
                                e.f.splice(j, 1);
                                break;
                            }
                            !e.f.length && delete e.f;
                        }
                        for (key in e.n) if (e.n[has](key) && e.n[key].f) {
                            var funcs = e.n[key].f;
                            for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
                                funcs.splice(j, 1);
                                break;
                            }
                            !funcs.length && delete e.n[key].f;
                        }
                    } else {
                        delete e.f;
                        for (key in e.n) if (e.n[has](key) && e.n[key].f) {
                            delete e.n[key].f;
                        }
                    }
                    e = e.n;
                }
            }
        };
        /*\
         * eve.once
         [ method ]
         **
         * Binds given event handler with a given name to only run once then unbind itself.
         | eve.once("login", f);
         | eve("login"); // triggers f
         | eve("login"); // no listeners
         * Use @eve to trigger the listener.
         **
         > Arguments
         **
         - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
         - f (function) event handler function
         **
         = (function) same return function as @eve.on
        \*/
        eve.once = function (name, f) {
            var f2 = function () {
                eve.unbind(name, f2);
                return f.apply(this, arguments);
            };
            return eve.on(name, f2);
        };
        /*\
         * eve.version
         [ property (string) ]
         **
         * Current version of the library.
        \*/
        eve.version = version;
        eve.toString = function () {
            return "You are running Eve " + version;
        };
    
        // life, atom环境不一样
        define("eve", [], function() { return eve; });
        return;
    
        (typeof module != "undefined" && module.exports) ? 
            (module.exports = eve) : 
            (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
    })(this);
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ "Raphaël 2.1.0" - JavaScript Vector Library                         │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function (glob, factory) {
        // AMD support
        if (typeof define === "function" && define.amd) {
            // Define as an anonymous module
            define('raphael',["eve"], function( eve ) {
                return factory(glob, eve);
            });
        } else {
            // Browser globals (glob is window)
            // Raphael adds itself to window
            factory(glob, glob.eve);
        }
    }(this, function (window, eve) {
        /*\
         * Raphael
         [ method ]
         **
         * Creates a canvas object on which to draw.
         * You must do this first, as all future calls to drawing methods
         * from this instance will be bound to this canvas.
         > Parameters
         **
         - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface
         - width (number)
         - height (number)
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - x (number)
         - y (number)
         - width (number)
         - height (number)
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add.
         - callback (function) #optional callback function which is going to be executed in the context of newly created paper
         * or
         - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`.
         = (object) @Paper
         > Usage
         | // Each of the following examples create a canvas
         | // that is 320px wide by 200px high.
         | // Canvas is created at the viewport’s 10,50 coordinate.
         | var paper = Raphael(10, 50, 320, 200);
         | // Canvas is created at the top left corner of the #notepad element
         | // (or its top right corner in dir="rtl" elements)
         | var paper = Raphael(document.getElementById("notepad"), 320, 200);
         | // Same as above
         | var paper = Raphael("notepad", 320, 200);
         | // Image dump
         | var set = Raphael(["notepad", 320, 200, {
         |     type: "rect",
         |     x: 10,
         |     y: 10,
         |     width: 25,
         |     height: 25,
         |     stroke: "#f00"
         | }, {
         |     type: "text",
         |     x: 30,
         |     y: 40,
         |     text: "Dump"
         | }]);
        \*/
        function R(first) {
            if (R.is(first, "function")) {
                return loaded ? first() : eve.on("raphael.DOMload", first);
            } else if (R.is(first, array)) {
                return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
            } else {
                var args = Array.prototype.slice.call(arguments, 0);
                if (R.is(args[args.length - 1], "function")) {
                    var f = args.pop();
                    return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
                        f.call(R._engine.create[apply](R, args));
                    });
                } else {
                    return R._engine.create[apply](R, arguments);
                }
            }
        }
        R.version = "2.1.0";
        R.eve = eve;
        var loaded,
            separator = /[, ]+/,
            elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
            formatrg = /\{(\d+)\}/g,
            proto = "prototype",
            has = "hasOwnProperty",
            g = {
                doc: document,
                win: window
            },
            oldRaphael = {
                was: Object.prototype[has].call(g.win, "Raphael"),
                is: g.win.Raphael
            },
            Paper = function () {
                /*\
                 * Paper.ca
                 [ property (object) ]
                 **
                 * Shortcut for @Paper.customAttributes
                \*/
                /*\
                 * Paper.customAttributes
                 [ property (object) ]
                 **
                 * If you have a set of attributes that you would like to represent
                 * as a function of some number you can do it easily with custom attributes:
                 > Usage
                 | paper.customAttributes.hue = function (num) {
                 |     num = num % 1;
                 |     return {fill: "hsb(" + num + ", 0.75, 1)"};
                 | };
                 | // Custom attribute “hue” will change fill
                 | // to be given hue with fixed saturation and brightness.
                 | // Now you can use it like this:
                 | var c = paper.circle(10, 10, 10).attr({hue: .45});
                 | // or even like this:
                 | c.animate({hue: 1}, 1e3);
                 | 
                 | // You could also create custom attribute
                 | // with multiple parameters:
                 | paper.customAttributes.hsb = function (h, s, b) {
                 |     return {fill: "hsb(" + [h, s, b].join(",") + ")"};
                 | };
                 | c.attr({hsb: "0.5 .8 1"});
                 | c.animate({hsb: [1, 0, 0.5]}, 1e3);
                \*/
                this.ca = this.customAttributes = {};
            },
            paperproto,
            appendChild = "appendChild",
            apply = "apply",
            concat = "concat",
            supportsTouch = ('ontouchstart' in g.win) || g.win.DocumentTouch && g.doc instanceof DocumentTouch, //taken from Modernizr touch test
            E = "",
            S = " ",
            Str = String,
            split = "split",
            events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
            touchMap = {
                mousedown: "touchstart",
                mousemove: "touchmove",
                mouseup: "touchend"
            },
            lowerCase = Str.prototype.toLowerCase,
            math = Math,
            mmax = math.max,
            mmin = math.min,
            abs = math.abs,
            pow = math.pow,
            PI = math.PI,
            nu = "number",
            string = "string",
            array = "array",
            toString = "toString",
            fillString = "fill",
            objectToString = Object.prototype.toString,
            paper = {},
            push = "push",
            ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
            colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
            isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
            bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
            round = math.round,
            setAttribute = "setAttribute",
            toFloat = parseFloat,
            toInt = parseInt,
            upperCase = Str.prototype.toUpperCase,
            availableAttrs = R._availableAttrs = {
                "arrow-end": "none",
                "arrow-start": "none",
                blur: 0,
                "clip-rect": "0 0 1e9 1e9",
                cursor: "default",
                cx: 0,
                cy: 0,
                fill: "#fff",
                "fill-opacity": 1,
                font: '10px "Arial"',
                "font-family": '"Arial"',
                "font-size": "10",
                "font-style": "normal",
                "font-weight": 400,
                gradient: 0,
                height: 0,
                href: "http://raphaeljs.com/",
                "letter-spacing": 0,
                opacity: 1,
                path: "M0,0",
                r: 0,
                rx: 0,
                ry: 0,
                src: "",
                stroke: "#000",
                "stroke-dasharray": "",
                "stroke-linecap": "butt",
                "stroke-linejoin": "butt",
                "stroke-miterlimit": 0,
                "stroke-opacity": 1,
                "stroke-width": 1,
                target: "_blank",
                "text-anchor": "middle",
                title: "Raphael",
                transform: "",
                width: 0,
                x: 0,
                y: 0
            },
            availableAnimAttrs = R._availableAnimAttrs = {
                blur: nu,
                "clip-rect": "csv",
                cx: nu,
                cy: nu,
                fill: "colour",
                "fill-opacity": nu,
                "font-size": nu,
                height: nu,
                opacity: nu,
                path: "path",
                r: nu,
                rx: nu,
                ry: nu,
                stroke: "colour",
                "stroke-opacity": nu,
                "stroke-width": nu,
                transform: "transform",
                width: nu,
                x: nu,
                y: nu
            },
            whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
            commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
            hsrg = {hs: 1, rg: 1},
            p2s = /,?([achlmqrstvxz]),?/gi,
            pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
            tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
            pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
            radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
            eldata = {},
            sortByKey = function (a, b) {
                return a.key - b.key;
            },
            sortByNumber = function (a, b) {
                return toFloat(a) - toFloat(b);
            },
            fun = function () {},
            pipe = function (x) {
                return x;
            },
            rectPath = R._rectPath = function (x, y, w, h, r) {
                if (r) {
                    return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
                }
                return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
            },
            ellipsePath = function (x, y, rx, ry) {
                if (ry == null) {
                    ry = rx;
                }
                return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
            },
            getPath = R._getPath = {
                path: function (el) {
                    return el.attr("path");
                },
                circle: function (el) {
                    var a = el.attrs;
                    return ellipsePath(a.cx, a.cy, a.r);
                },
                ellipse: function (el) {
                    var a = el.attrs;
                    return ellipsePath(a.cx, a.cy, a.rx, a.ry);
                },
                rect: function (el) {
                    var a = el.attrs;
                    return rectPath(a.x, a.y, a.width, a.height, a.r);
                },
                image: function (el) {
                    var a = el.attrs;
                    return rectPath(a.x, a.y, a.width, a.height);
                },
                text: function (el) {
                    var bbox = el._getBBox();
                    return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
                },
                set : function(el) {
                    var bbox = el._getBBox();
                    return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
                }
            },
            /*\
             * Raphael.mapPath
             [ method ]
             **
             * Transform the path string with given matrix.
             > Parameters
             - path (string) path string
             - matrix (object) see @Matrix
             = (string) transformed path string
            \*/
            mapPath = R.mapPath = function (path, matrix) {
                if (!matrix) {
                    return path;
                }
                var x, y, i, j, ii, jj, pathi;
                path = path2curve(path);
                for (i = 0, ii = path.length; i < ii; i++) {
                    pathi = path[i];
                    for (j = 1, jj = pathi.length; j < jj; j += 2) {
                        x = matrix.x(pathi[j], pathi[j + 1]);
                        y = matrix.y(pathi[j], pathi[j + 1]);
                        pathi[j] = x;
                        pathi[j + 1] = y;
                    }
                }
                return path;
            };
    
        R._g = g;
        /*\
         * Raphael.type
         [ property (string) ]
         **
         * Can be “SVG”, “VML” or empty, depending on browser support.
        \*/
        R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
        if (R.type == "VML") {
            var d = g.doc.createElement("div"),
                b;
            d.innerHTML = '<v:shape adj="1"/>';
            b = d.firstChild;
            b.style.behavior = "url(#default#VML)";
            if (!(b && typeof b.adj == "object")) {
                return (R.type = E);
            }
            d = null;
        }
        /*\
         * Raphael.svg
         [ property (boolean) ]
         **
         * `true` if browser supports SVG.
        \*/
        /*\
         * Raphael.vml
         [ property (boolean) ]
         **
         * `true` if browser supports VML.
        \*/
        R.svg = !(R.vml = R.type == "VML");
        R._Paper = Paper;
        /*\
         * Raphael.fn
         [ property (object) ]
         **
         * You can add your own method to the canvas. For example if you want to draw a pie chart,
         * you can create your own pie chart function and ship it as a Raphaël plugin. To do this
         * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a
         * Raphaël instance is created, otherwise it will take no effect. Please note that the
         * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to
         * ensure any namespacing ensures proper context.
         > Usage
         | Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
         |     return this.path( ... );
         | };
         | // or create namespace
         | Raphael.fn.mystuff = {
         |     arrow: function () {…},
         |     star: function () {…},
         |     // etc…
         | };
         | var paper = Raphael(10, 10, 630, 480);
         | // then use it
         | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"});
         | paper.mystuff.arrow();
         | paper.mystuff.star();
        \*/
        R.fn = paperproto = Paper.prototype = R.prototype;
        R._id = 0;
        R._oid = 0;
        /*\
         * Raphael.is
         [ method ]
         **
         * Handfull replacement for `typeof` operator.
         > Parameters
         - o (…) any object or primitive
         - type (string) name of the type, i.e. “string”, “function”, “number”, etc.
         = (boolean) is given value is of given type
        \*/
        R.is = function (o, type) {
            type = lowerCase.call(type);
            if (type == "finite") {
                return !isnan[has](+o);
            }
            if (type == "array") {
                return o instanceof Array;
            }
            return  (type == "null" && o === null) ||
                    (type == typeof o && o !== null) ||
                    (type == "object" && o === Object(o)) ||
                    (type == "array" && Array.isArray && Array.isArray(o)) ||
                    objectToString.call(o).slice(8, -1).toLowerCase() == type;
        };
    
        function clone(obj) {
            if (typeof obj == "function" || Object(obj) !== obj) {
                return obj;
            }
            var res = new obj.constructor;
            for (var key in obj) if (obj[has](key)) {
                res[key] = clone(obj[key]);
            }
            return res;
        }
    
        /*\
         * Raphael.angle
         [ method ]
         **
         * Returns angle between two or three points
         > Parameters
         - x1 (number) x coord of first point
         - y1 (number) y coord of first point
         - x2 (number) x coord of second point
         - y2 (number) y coord of second point
         - x3 (number) #optional x coord of third point
         - y3 (number) #optional y coord of third point
         = (number) angle in degrees.
        \*/
        R.angle = function (x1, y1, x2, y2, x3, y3) {
            if (x3 == null) {
                var x = x1 - x2,
                    y = y1 - y2;
                if (!x && !y) {
                    return 0;
                }
                return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
            } else {
                return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
            }
        };
        /*\
         * Raphael.rad
         [ method ]
         **
         * Transform angle to radians
         > Parameters
         - deg (number) angle in degrees
         = (number) angle in radians.
        \*/
        R.rad = function (deg) {
            return deg % 360 * PI / 180;
        };
        /*\
         * Raphael.deg
         [ method ]
         **
         * Transform angle to degrees
         > Parameters
         - deg (number) angle in radians
         = (number) angle in degrees.
        \*/
        R.deg = function (rad) {
            return rad * 180 / PI % 360;
        };
        /*\
         * Raphael.snapTo
         [ method ]
         **
         * Snaps given value to given grid.
         > Parameters
         - values (array|number) given array of values or step of the grid
         - value (number) value to adjust
         - tolerance (number) #optional tolerance for snapping. Default is `10`.
         = (number) adjusted value.
        \*/
        R.snapTo = function (values, value, tolerance) {
            tolerance = R.is(tolerance, "finite") ? tolerance : 10;
            if (R.is(values, array)) {
                var i = values.length;
                while (i--) if (abs(values[i] - value) <= tolerance) {
                    return values[i];
                }
            } else {
                values = +values;
                var rem = value % values;
                if (rem < tolerance) {
                    return value - rem;
                }
                if (rem > values - tolerance) {
                    return value - rem + values;
                }
            }
            return value;
        };
    
        /*\
         * Raphael.createUUID
         [ method ]
         **
         * Returns RFC4122, version 4 ID
        \*/
        var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
            return function () {
                return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
            };
        })(/[xy]/g, function (c) {
            var r = math.random() * 16 | 0,
                v = c == "x" ? r : (r & 3 | 8);
            return v.toString(16);
        });
    
        /*\
         * Raphael.setWindow
         [ method ]
         **
         * Used when you need to draw in `&lt;iframe>`. Switched window to the iframe one.
         > Parameters
         - newwin (window) new window object
        \*/
        R.setWindow = function (newwin) {
            eve("raphael.setWindow", R, g.win, newwin);
            g.win = newwin;
            g.doc = g.win.document;
            if (R._engine.initWin) {
                R._engine.initWin(g.win);
            }
        };
        var toHex = function (color) {
            if (R.vml) {
                // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
                var trim = /^\s+|\s+$/g;
                var bod;
                try {
                    var docum = new ActiveXObject("htmlfile");
                    docum.write("<body>");
                    docum.close();
                    bod = docum.body;
                } catch(e) {
                    bod = createPopup().document.body;
                }
                var range = bod.createTextRange();
                toHex = cacher(function (color) {
                    try {
                        bod.style.color = Str(color).replace(trim, E);
                        var value = range.queryCommandValue("ForeColor");
                        value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
                        return "#" + ("000000" + value.toString(16)).slice(-6);
                    } catch(e) {
                        return "none";
                    }
                });
            } else {
                var i = g.doc.createElement("i");
                i.title = "Rapha\xebl Colour Picker";
                i.style.display = "none";
                g.doc.body.appendChild(i);
                toHex = cacher(function (color) {
                    i.style.color = color;
                    return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
                });
            }
            return toHex(color);
        },
        hsbtoString = function () {
            return "hsb(" + [this.h, this.s, this.b] + ")";
        },
        hsltoString = function () {
            return "hsl(" + [this.h, this.s, this.l] + ")";
        },
        rgbtoString = function () {
            return this.hex;
        },
        prepareRGB = function (r, g, b) {
            if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
                b = r.b;
                g = r.g;
                r = r.r;
            }
            if (g == null && R.is(r, string)) {
                var clr = R.getRGB(r);
                r = clr.r;
                g = clr.g;
                b = clr.b;
            }
            if (r > 1 || g > 1 || b > 1) {
                r /= 255;
                g /= 255;
                b /= 255;
            }
    
            return [r, g, b];
        },
        packageRGB = function (r, g, b, o) {
            r *= 255;
            g *= 255;
            b *= 255;
            var rgb = {
                r: r,
                g: g,
                b: b,
                hex: R.rgb(r, g, b),
                toString: rgbtoString
            };
            R.is(o, "finite") && (rgb.opacity = o);
            return rgb;
        };
    
        /*\
         * Raphael.color
         [ method ]
         **
         * Parses the color string and returns object with all values for the given color.
         > Parameters
         - clr (string) color string in one of the supported formats (see @Raphael.getRGB)
         = (object) Combined RGB & HSB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••,
         o     error (boolean) `true` if string can’t be parsed,
         o     h (number) hue,
         o     s (number) saturation,
         o     v (number) value (brightness),
         o     l (number) lightness
         o }
        \*/
        R.color = function (clr) {
            var rgb;
            if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
                rgb = R.hsb2rgb(clr);
                clr.r = rgb.r;
                clr.g = rgb.g;
                clr.b = rgb.b;
                clr.hex = rgb.hex;
            } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
                rgb = R.hsl2rgb(clr);
                clr.r = rgb.r;
                clr.g = rgb.g;
                clr.b = rgb.b;
                clr.hex = rgb.hex;
            } else {
                if (R.is(clr, "string")) {
                    clr = R.getRGB(clr);
                }
                if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
                    rgb = R.rgb2hsl(clr);
                    clr.h = rgb.h;
                    clr.s = rgb.s;
                    clr.l = rgb.l;
                    rgb = R.rgb2hsb(clr);
                    clr.v = rgb.b;
                } else {
                    clr = {hex: "none"};
                    clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
                }
            }
            clr.toString = rgbtoString;
            return clr;
        };
        /*\
         * Raphael.hsb2rgb
         [ method ]
         **
         * Converts HSB values to RGB object.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - v (number) value or brightness
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••
         o }
        \*/
        R.hsb2rgb = function (h, s, v, o) {
            if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
                v = h.b;
                s = h.s;
                h = h.h;
                o = h.o;
            }
            h *= 360;
            var R, G, B, X, C;
            h = (h % 360) / 60;
            C = v * s;
            X = C * (1 - abs(h % 2 - 1));
            R = G = B = v - C;
    
            h = ~~h;
            R += [C, X, 0, 0, X, C][h];
            G += [X, C, C, X, 0, 0][h];
            B += [0, 0, X, C, C, X][h];
            return packageRGB(R, G, B, o);
        };
        /*\
         * Raphael.hsl2rgb
         [ method ]
         **
         * Converts HSL values to RGB object.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - l (number) luminosity
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue,
         o     hex (string) color in HTML/CSS format: #••••••
         o }
        \*/
        R.hsl2rgb = function (h, s, l, o) {
            if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
                l = h.l;
                s = h.s;
                h = h.h;
            }
            if (h > 1 || s > 1 || l > 1) {
                h /= 360;
                s /= 100;
                l /= 100;
            }
            h *= 360;
            var R, G, B, X, C;
            h = (h % 360) / 60;
            C = 2 * s * (l < .5 ? l : 1 - l);
            X = C * (1 - abs(h % 2 - 1));
            R = G = B = l - C / 2;
    
            h = ~~h;
            R += [C, X, 0, 0, X, C][h];
            G += [X, C, C, X, 0, 0][h];
            B += [0, 0, X, C, C, X][h];
            return packageRGB(R, G, B, o);
        };
        /*\
         * Raphael.rgb2hsb
         [ method ]
         **
         * Converts RGB values to HSB object.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (object) HSB object in format:
         o {
         o     h (number) hue
         o     s (number) saturation
         o     b (number) brightness
         o }
        \*/
        R.rgb2hsb = function (r, g, b) {
            b = prepareRGB(r, g, b);
            r = b[0];
            g = b[1];
            b = b[2];
    
            var H, S, V, C;
            V = mmax(r, g, b);
            C = V - mmin(r, g, b);
            H = (C == 0 ? null :
                 V == r ? (g - b) / C :
                 V == g ? (b - r) / C + 2 :
                          (r - g) / C + 4
                );
            H = ((H + 360) % 6) * 60 / 360;
            S = C == 0 ? 0 : C / V;
            return {h: H, s: S, b: V, toString: hsbtoString};
        };
        /*\
         * Raphael.rgb2hsl
         [ method ]
         **
         * Converts RGB values to HSL object.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (object) HSL object in format:
         o {
         o     h (number) hue
         o     s (number) saturation
         o     l (number) luminosity
         o }
        \*/
        R.rgb2hsl = function (r, g, b) {
            b = prepareRGB(r, g, b);
            r = b[0];
            g = b[1];
            b = b[2];
    
            var H, S, L, M, m, C;
            M = mmax(r, g, b);
            m = mmin(r, g, b);
            C = M - m;
            H = (C == 0 ? null :
                 M == r ? (g - b) / C :
                 M == g ? (b - r) / C + 2 :
                          (r - g) / C + 4);
            H = ((H + 360) % 6) * 60 / 360;
            L = (M + m) / 2;
            S = (C == 0 ? 0 :
                 L < .5 ? C / (2 * L) :
                          C / (2 - 2 * L));
            return {h: H, s: S, l: L, toString: hsltoString};
        };
        R._path2string = function () {
            return this.join(",").replace(p2s, "$1");
        };
        function repush(array, item) {
            for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
                return array.push(array.splice(i, 1)[0]);
            }
        }
        function cacher(f, scope, postprocessor) {
            function newf() {
                var arg = Array.prototype.slice.call(arguments, 0),
                    args = arg.join("\u2400"),
                    cache = newf.cache = newf.cache || {},
                    count = newf.count = newf.count || [];
                if (cache[has](args)) {
                    repush(count, args);
                    return postprocessor ? postprocessor(cache[args]) : cache[args];
                }
                count.length >= 1e3 && delete cache[count.shift()];
                count.push(args);
                cache[args] = f[apply](scope, arg);
                return postprocessor ? postprocessor(cache[args]) : cache[args];
            }
            return newf;
        }
    
        var preload = R._preload = function (src, f) {
            var img = g.doc.createElement("img");
            img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
            img.onload = function () {
                f.call(this);
                this.onload = null;
                g.doc.body.removeChild(this);
            };
            img.onerror = function () {
                g.doc.body.removeChild(this);
            };
            g.doc.body.appendChild(img);
            img.src = src;
        };
    
        function clrToString() {
            return this.hex;
        }
    
        /*\
         * Raphael.getRGB
         [ method ]
         **
         * Parses colour string as RGB object
         > Parameters
         - colour (string) colour string in one of formats:
         # <ul>
         #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
         #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
         #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
         #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
         #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
         #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
         #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsl(•••, •••, •••) — same as hsb</li>
         #     <li>hsl(•••%, •••%, •••%) — same as hsb</li>
         # </ul>
         = (object) RGB object in format:
         o {
         o     r (number) red,
         o     g (number) green,
         o     b (number) blue
         o     hex (string) color in HTML/CSS format: #••••••,
         o     error (boolean) true if string can’t be parsed
         o }
        \*/
        R.getRGB = cacher(function (colour) {
            if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
                return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
            }
            if (colour == "none") {
                return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
            }
            !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
            var res,
                red,
                green,
                blue,
                opacity,
                t,
                values,
                rgb = colour.match(colourRegExp);
            if (rgb) {
                if (rgb[2]) {
                    blue = toInt(rgb[2].substring(5), 16);
                    green = toInt(rgb[2].substring(3, 5), 16);
                    red = toInt(rgb[2].substring(1, 3), 16);
                }
                if (rgb[3]) {
                    blue = toInt((t = rgb[3].charAt(3)) + t, 16);
                    green = toInt((t = rgb[3].charAt(2)) + t, 16);
                    red = toInt((t = rgb[3].charAt(1)) + t, 16);
                }
                if (rgb[4]) {
                    values = rgb[4][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                }
                if (rgb[5]) {
                    values = rgb[5][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
                    rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                    return R.hsb2rgb(red, green, blue, opacity);
                }
                if (rgb[6]) {
                    values = rgb[6][split](commaSpaces);
                    red = toFloat(values[0]);
                    values[0].slice(-1) == "%" && (red *= 2.55);
                    green = toFloat(values[1]);
                    values[1].slice(-1) == "%" && (green *= 2.55);
                    blue = toFloat(values[2]);
                    values[2].slice(-1) == "%" && (blue *= 2.55);
                    (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
                    rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
                    values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
                    return R.hsl2rgb(red, green, blue, opacity);
                }
                rgb = {r: red, g: green, b: blue, toString: clrToString};
                rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
                R.is(opacity, "finite") && (rgb.opacity = opacity);
                return rgb;
            }
            return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
        }, R);
        /*\
         * Raphael.hsb
         [ method ]
         **
         * Converts HSB values to hex representation of the colour.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - b (number) value or brightness
         = (string) hex representation of the colour.
        \*/
        R.hsb = cacher(function (h, s, b) {
            return R.hsb2rgb(h, s, b).hex;
        });
        /*\
         * Raphael.hsl
         [ method ]
         **
         * Converts HSL values to hex representation of the colour.
         > Parameters
         - h (number) hue
         - s (number) saturation
         - l (number) luminosity
         = (string) hex representation of the colour.
        \*/
        R.hsl = cacher(function (h, s, l) {
            return R.hsl2rgb(h, s, l).hex;
        });
        /*\
         * Raphael.rgb
         [ method ]
         **
         * Converts RGB values to hex representation of the colour.
         > Parameters
         - r (number) red
         - g (number) green
         - b (number) blue
         = (string) hex representation of the colour.
        \*/
        R.rgb = cacher(function (r, g, b) {
            return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
        });
        /*\
         * Raphael.getColor
         [ method ]
         **
         * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset
         > Parameters
         - value (number) #optional brightness, default is `0.75`
         = (string) hex representation of the colour.
        \*/
        R.getColor = function (value) {
            var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
                rgb = this.hsb2rgb(start.h, start.s, start.b);
            start.h += .075;
            if (start.h > 1) {
                start.h = 0;
                start.s -= .2;
                start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
            }
            return rgb.hex;
        };
        /*\
         * Raphael.getColor.reset
         [ method ]
         **
         * Resets spectrum position for @Raphael.getColor back to red.
        \*/
        R.getColor.reset = function () {
            delete this.start;
        };
    
        // http://schepers.cc/getting-to-the-point
        function catmullRom2bezier(crp, z) {
            var d = [];
            for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
                var p = [
                            {x: +crp[i - 2], y: +crp[i - 1]},
                            {x: +crp[i],     y: +crp[i + 1]},
                            {x: +crp[i + 2], y: +crp[i + 3]},
                            {x: +crp[i + 4], y: +crp[i + 5]}
                        ];
                if (z) {
                    if (!i) {
                        p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
                    } else if (iLen - 4 == i) {
                        p[3] = {x: +crp[0], y: +crp[1]};
                    } else if (iLen - 2 == i) {
                        p[2] = {x: +crp[0], y: +crp[1]};
                        p[3] = {x: +crp[2], y: +crp[3]};
                    }
                } else {
                    if (iLen - 4 == i) {
                        p[3] = p[2];
                    } else if (!i) {
                        p[0] = {x: +crp[i], y: +crp[i + 1]};
                    }
                }
                d.push(["C",
                      (-p[0].x + 6 * p[1].x + p[2].x) / 6,
                      (-p[0].y + 6 * p[1].y + p[2].y) / 6,
                      (p[1].x + 6 * p[2].x - p[3].x) / 6,
                      (p[1].y + 6*p[2].y - p[3].y) / 6,
                      p[2].x,
                      p[2].y
                ]);
            }
    
            return d;
        }
        /*\
         * Raphael.parsePathString
         [ method ]
         **
         * Utility method
         **
         * Parses given path string into an array of arrays of path segments.
         > Parameters
         - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
         = (array) array of segments.
        \*/
        R.parsePathString = function (pathString) {
            if (!pathString) {
                return null;
            }
            var pth = paths(pathString);
            if (pth.arr) {
                return pathClone(pth.arr);
            }
    
            var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
                data = [];
            if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
                data = pathClone(pathString);
            }
            if (!data.length) {
                Str(pathString).replace(pathCommand, function (a, b, c) {
                    var params = [],
                        name = b.toLowerCase();
                    c.replace(pathValues, function (a, b) {
                        b && params.push(+b);
                    });
                    if (name == "m" && params.length > 2) {
                        data.push([b][concat](params.splice(0, 2)));
                        name = "l";
                        b = b == "m" ? "l" : "L";
                    }
                    if (name == "r") {
                        data.push([b][concat](params));
                    } else while (params.length >= paramCounts[name]) {
                        data.push([b][concat](params.splice(0, paramCounts[name])));
                        if (!paramCounts[name]) {
                            break;
                        }
                    }
                });
            }
            data.toString = R._path2string;
            pth.arr = pathClone(data);
            return data;
        };
        /*\
         * Raphael.parseTransformString
         [ method ]
         **
         * Utility method
         **
         * Parses given path string into an array of transformations.
         > Parameters
         - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
         = (array) array of transformations.
        \*/
        R.parseTransformString = cacher(function (TString) {
            if (!TString) {
                return null;
            }
            var paramCounts = {r: 3, s: 4, t: 2, m: 6},
                data = [];
            if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
                data = pathClone(TString);
            }
            if (!data.length) {
                Str(TString).replace(tCommand, function (a, b, c) {
                    var params = [],
                        name = lowerCase.call(b);
                    c.replace(pathValues, function (a, b) {
                        b && params.push(+b);
                    });
                    data.push([b][concat](params));
                });
            }
            data.toString = R._path2string;
            return data;
        });
        // PATHS
        var paths = function (ps) {
            var p = paths.ps = paths.ps || {};
            if (p[ps]) {
                p[ps].sleep = 100;
            } else {
                p[ps] = {
                    sleep: 100
                };
            }
            setTimeout(function () {
                for (var key in p) if (p[has](key) && key != ps) {
                    p[key].sleep--;
                    !p[key].sleep && delete p[key];
                }
            });
            return p[ps];
        };
        /*\
         * Raphael.findDotsAtSegment
         [ method ]
         **
         * Utility method
         **
         * Find dot coordinates on the given cubic bezier curve at the given t.
         > Parameters
         - p1x (number) x of the first point of the curve
         - p1y (number) y of the first point of the curve
         - c1x (number) x of the first anchor of the curve
         - c1y (number) y of the first anchor of the curve
         - c2x (number) x of the second anchor of the curve
         - c2y (number) y of the second anchor of the curve
         - p2x (number) x of the second point of the curve
         - p2y (number) y of the second point of the curve
         - t (number) position on the curve (0..1)
         = (object) point information in format:
         o {
         o     x: (number) x coordinate of the point
         o     y: (number) y coordinate of the point
         o     m: {
         o         x: (number) x coordinate of the left anchor
         o         y: (number) y coordinate of the left anchor
         o     }
         o     n: {
         o         x: (number) x coordinate of the right anchor
         o         y: (number) y coordinate of the right anchor
         o     }
         o     start: {
         o         x: (number) x coordinate of the start of the curve
         o         y: (number) y coordinate of the start of the curve
         o     }
         o     end: {
         o         x: (number) x coordinate of the end of the curve
         o         y: (number) y coordinate of the end of the curve
         o     }
         o     alpha: (number) angle of the curve derivative at the point
         o }
        \*/
        R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
            var t1 = 1 - t,
                t13 = pow(t1, 3),
                t12 = pow(t1, 2),
                t2 = t * t,
                t3 = t2 * t,
                x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
                y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
                mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
                my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
                nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
                ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
                ax = t1 * p1x + t * c1x,
                ay = t1 * p1y + t * c1y,
                cx = t1 * c2x + t * p2x,
                cy = t1 * c2y + t * p2y,
                alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
            (mx > nx || my < ny) && (alpha += 180);
            return {
                x: x,
                y: y,
                m: {x: mx, y: my},
                n: {x: nx, y: ny},
                start: {x: ax, y: ay},
                end: {x: cx, y: cy},
                alpha: alpha
            };
        };
        /*\
         * Raphael.bezierBBox
         [ method ]
         **
         * Utility method
         **
         * Return bounding box of a given cubic bezier curve
         > Parameters
         - p1x (number) x of the first point of the curve
         - p1y (number) y of the first point of the curve
         - c1x (number) x of the first anchor of the curve
         - c1y (number) y of the first anchor of the curve
         - c2x (number) x of the second anchor of the curve
         - c2y (number) y of the second anchor of the curve
         - p2x (number) x of the second point of the curve
         - p2y (number) y of the second point of the curve
         * or
         - bez (array) array of six points for bezier curve
         = (object) point information in format:
         o {
         o     min: {
         o         x: (number) x coordinate of the left point
         o         y: (number) y coordinate of the top point
         o     }
         o     max: {
         o         x: (number) x coordinate of the right point
         o         y: (number) y coordinate of the bottom point
         o     }
         o }
        \*/
        R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
            if (!R.is(p1x, "array")) {
                p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
            }
            var bbox = curveDim.apply(null, p1x);
            return {
                x: bbox.min.x,
                y: bbox.min.y,
                x2: bbox.max.x,
                y2: bbox.max.y,
                width: bbox.max.x - bbox.min.x,
                height: bbox.max.y - bbox.min.y
            };
        };
        /*\
         * Raphael.isPointInsideBBox
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if given point is inside bounding boxes.
         > Parameters
         - bbox (string) bounding box
         - x (string) x coordinate of the point
         - y (string) y coordinate of the point
         = (boolean) `true` if point inside
        \*/
        R.isPointInsideBBox = function (bbox, x, y) {
            return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
        };
        /*\
         * Raphael.isBBoxIntersect
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if two bounding boxes intersect
         > Parameters
         - bbox1 (string) first bounding box
         - bbox2 (string) second bounding box
         = (boolean) `true` if they intersect
        \*/
        R.isBBoxIntersect = function (bbox1, bbox2) {
            var i = R.isPointInsideBBox;
            return i(bbox2, bbox1.x, bbox1.y)
                || i(bbox2, bbox1.x2, bbox1.y)
                || i(bbox2, bbox1.x, bbox1.y2)
                || i(bbox2, bbox1.x2, bbox1.y2)
                || i(bbox1, bbox2.x, bbox2.y)
                || i(bbox1, bbox2.x2, bbox2.y)
                || i(bbox1, bbox2.x, bbox2.y2)
                || i(bbox1, bbox2.x2, bbox2.y2)
                || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
                && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
        };
        function base3(t, p1, p2, p3, p4) {
            var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
                t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
            return t * t2 - 3 * p1 + 3 * p2;
        }
        function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
            if (z == null) {
                z = 1;
            }
            z = z > 1 ? 1 : z < 0 ? 0 : z;
            var z2 = z / 2,
                n = 12,
                Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
                Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
                sum = 0;
            for (var i = 0; i < n; i++) {
                var ct = z2 * Tvalues[i] + z2,
                    xbase = base3(ct, x1, x2, x3, x4),
                    ybase = base3(ct, y1, y2, y3, y4),
                    comb = xbase * xbase + ybase * ybase;
                sum += Cvalues[i] * math.sqrt(comb);
            }
            return z2 * sum;
        }
        function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
            if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
                return;
            }
            var t = 1,
                step = t / 2,
                t2 = t - step,
                l,
                e = .01;
            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
            while (abs(l - ll) > e) {
                step /= 2;
                t2 += (l < ll ? 1 : -1) * step;
                l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
            }
            return t2;
        }
        function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
            if (
                mmax(x1, x2) < mmin(x3, x4) ||
                mmin(x1, x2) > mmax(x3, x4) ||
                mmax(y1, y2) < mmin(y3, y4) ||
                mmin(y1, y2) > mmax(y3, y4)
            ) {
                return;
            }
            var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
                ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
                denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    
            if (!denominator) {
                return;
            }
            var px = nx / denominator,
                py = ny / denominator,
                px2 = +px.toFixed(2),
                py2 = +py.toFixed(2);
            if (
                px2 < +mmin(x1, x2).toFixed(2) ||
                px2 > +mmax(x1, x2).toFixed(2) ||
                px2 < +mmin(x3, x4).toFixed(2) ||
                px2 > +mmax(x3, x4).toFixed(2) ||
                py2 < +mmin(y1, y2).toFixed(2) ||
                py2 > +mmax(y1, y2).toFixed(2) ||
                py2 < +mmin(y3, y4).toFixed(2) ||
                py2 > +mmax(y3, y4).toFixed(2)
            ) {
                return;
            }
            return {x: px, y: py};
        }
        function inter(bez1, bez2) {
            return interHelper(bez1, bez2);
        }
        function interCount(bez1, bez2) {
            return interHelper(bez1, bez2, 1);
        }
        function interHelper(bez1, bez2, justCount) {
            var bbox1 = R.bezierBBox(bez1),
                bbox2 = R.bezierBBox(bez2);
            if (!R.isBBoxIntersect(bbox1, bbox2)) {
                return justCount ? 0 : [];
            }
            var l1 = bezlen.apply(0, bez1),
                l2 = bezlen.apply(0, bez2),
                n1 = mmax(~~(l1 / 5), 1),
                n2 = mmax(~~(l2 / 5), 1),
                dots1 = [],
                dots2 = [],
                xy = {},
                res = justCount ? 0 : [];
            for (var i = 0; i < n1 + 1; i++) {
                var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
                dots1.push({x: p.x, y: p.y, t: i / n1});
            }
            for (i = 0; i < n2 + 1; i++) {
                p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
                dots2.push({x: p.x, y: p.y, t: i / n2});
            }
            for (i = 0; i < n1; i++) {
                for (var j = 0; j < n2; j++) {
                    var di = dots1[i],
                        di1 = dots1[i + 1],
                        dj = dots2[j],
                        dj1 = dots2[j + 1],
                        ci = abs(di1.x - di.x) < .001 ? "y" : "x",
                        cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
                        is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
                    if (is) {
                        if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
                            continue;
                        }
                        xy[is.x.toFixed(4)] = is.y.toFixed(4);
                        var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
                            t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
                        if (t1 >= 0 && t1 <= 1.001 && t2 >= 0 && t2 <= 1.001) {
                            if (justCount) {
                                res++;
                            } else {
                                res.push({
                                    x: is.x,
                                    y: is.y,
                                    t1: mmin(t1, 1),
                                    t2: mmin(t2, 1)
                                });
                            }
                        }
                    }
                }
            }
            return res;
        }
        /*\
         * Raphael.pathIntersection
         [ method ]
         **
         * Utility method
         **
         * Finds intersections of two paths
         > Parameters
         - path1 (string) path string
         - path2 (string) path string
         = (array) dots of intersection
         o [
         o     {
         o         x: (number) x coordinate of the point
         o         y: (number) y coordinate of the point
         o         t1: (number) t value for segment of path1
         o         t2: (number) t value for segment of path2
         o         segment1: (number) order number for segment of path1
         o         segment2: (number) order number for segment of path2
         o         bez1: (array) eight coordinates representing beziér curve for the segment of path1
         o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
         o     }
         o ]
        \*/
        R.pathIntersection = function (path1, path2) {
            return interPathHelper(path1, path2);
        };
        R.pathIntersectionNumber = function (path1, path2) {
            return interPathHelper(path1, path2, 1);
        };
        function interPathHelper(path1, path2, justCount) {
            path1 = R._path2curve(path1);
            path2 = R._path2curve(path2);
            var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
                res = justCount ? 0 : [];
            for (var i = 0, ii = path1.length; i < ii; i++) {
                var pi = path1[i];
                if (pi[0] == "M") {
                    x1 = x1m = pi[1];
                    y1 = y1m = pi[2];
                } else {
                    if (pi[0] == "C") {
                        bez1 = [x1, y1].concat(pi.slice(1));
                        x1 = bez1[6];
                        y1 = bez1[7];
                    } else {
                        bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
                        x1 = x1m;
                        y1 = y1m;
                    }
                    for (var j = 0, jj = path2.length; j < jj; j++) {
                        var pj = path2[j];
                        if (pj[0] == "M") {
                            x2 = x2m = pj[1];
                            y2 = y2m = pj[2];
                        } else {
                            if (pj[0] == "C") {
                                bez2 = [x2, y2].concat(pj.slice(1));
                                x2 = bez2[6];
                                y2 = bez2[7];
                            } else {
                                bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
                                x2 = x2m;
                                y2 = y2m;
                            }
                            var intr = interHelper(bez1, bez2, justCount);
                            if (justCount) {
                                res += intr;
                            } else {
                                for (var k = 0, kk = intr.length; k < kk; k++) {
                                    intr[k].segment1 = i;
                                    intr[k].segment2 = j;
                                    intr[k].bez1 = bez1;
                                    intr[k].bez2 = bez2;
                                }
                                res = res.concat(intr);
                            }
                        }
                    }
                }
            }
            return res;
        }
        /*\
         * Raphael.isPointInsidePath
         [ method ]
         **
         * Utility method
         **
         * Returns `true` if given point is inside a given closed path.
         > Parameters
         - path (string) path string
         - x (number) x of the point
         - y (number) y of the point
         = (boolean) true, if point is inside the path
        \*/
        R.isPointInsidePath = function (path, x, y) {
            var bbox = R.pathBBox(path);
            return R.isPointInsideBBox(bbox, x, y) &&
                   interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
        };
        R._removedFactory = function (methodname) {
            return function () {
                eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
            };
        };
        /*\
         * Raphael.pathBBox
         [ method ]
         **
         * Utility method
         **
         * Return bounding box of a given path
         > Parameters
         - path (string) path string
         = (object) bounding box
         o {
         o     x: (number) x coordinate of the left top point of the box
         o     y: (number) y coordinate of the left top point of the box
         o     x2: (number) x coordinate of the right bottom point of the box
         o     y2: (number) y coordinate of the right bottom point of the box
         o     width: (number) width of the box
         o     height: (number) height of the box
         o     cx: (number) x coordinate of the center of the box
         o     cy: (number) y coordinate of the center of the box
         o }
        \*/
        var pathDimensions = R.pathBBox = function (path) {
            var pth = paths(path);
            if (pth.bbox) {
                return clone(pth.bbox);
            }
            if (!path) {
                return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
            }
            path = path2curve(path);
            var x = 0,
                y = 0,
                X = [],
                Y = [],
                p;
            for (var i = 0, ii = path.length; i < ii; i++) {
                p = path[i];
                if (p[0] == "M") {
                    x = p[1];
                    y = p[2];
                    X.push(x);
                    Y.push(y);
                } else {
                    var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
                    X = X[concat](dim.min.x, dim.max.x);
                    Y = Y[concat](dim.min.y, dim.max.y);
                    x = p[5];
                    y = p[6];
                }
            }
            var xmin = mmin[apply](0, X),
                ymin = mmin[apply](0, Y),
                xmax = mmax[apply](0, X),
                ymax = mmax[apply](0, Y),
                width = xmax - xmin,
                height = ymax - ymin,
                    bb = {
                    x: xmin,
                    y: ymin,
                    x2: xmax,
                    y2: ymax,
                    width: width,
                    height: height,
                    cx: xmin + width / 2,
                    cy: ymin + height / 2
                };
            pth.bbox = clone(bb);
            return bb;
        },
            pathClone = function (pathArray) {
                var res = clone(pathArray);
                res.toString = R._path2string;
                return res;
            },
            pathToRelative = R._pathToRelative = function (pathArray) {
                var pth = paths(pathArray);
                if (pth.rel) {
                    return pathClone(pth.rel);
                }
                if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
                    pathArray = R.parsePathString(pathArray);
                }
                var res = [],
                    x = 0,
                    y = 0,
                    mx = 0,
                    my = 0,
                    start = 0;
                if (pathArray[0][0] == "M") {
                    x = pathArray[0][1];
                    y = pathArray[0][2];
                    mx = x;
                    my = y;
                    start++;
                    res.push(["M", x, y]);
                }
                for (var i = start, ii = pathArray.length; i < ii; i++) {
                    var r = res[i] = [],
                        pa = pathArray[i];
                    if (pa[0] != lowerCase.call(pa[0])) {
                        r[0] = lowerCase.call(pa[0]);
                        switch (r[0]) {
                            case "a":
                                r[1] = pa[1];
                                r[2] = pa[2];
                                r[3] = pa[3];
                                r[4] = pa[4];
                                r[5] = pa[5];
                                r[6] = +(pa[6] - x).toFixed(3);
                                r[7] = +(pa[7] - y).toFixed(3);
                                break;
                            case "v":
                                r[1] = +(pa[1] - y).toFixed(3);
                                break;
                            case "m":
                                mx = pa[1];
                                my = pa[2];
                            default:
                                for (var j = 1, jj = pa.length; j < jj; j++) {
                                    r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
                                }
                        }
                    } else {
                        r = res[i] = [];
                        if (pa[0] == "m") {
                            mx = pa[1] + x;
                            my = pa[2] + y;
                        }
                        for (var k = 0, kk = pa.length; k < kk; k++) {
                            res[i][k] = pa[k];
                        }
                    }
                    var len = res[i].length;
                    switch (res[i][0]) {
                        case "z":
                            x = mx;
                            y = my;
                            break;
                        case "h":
                            x += +res[i][len - 1];
                            break;
                        case "v":
                            y += +res[i][len - 1];
                            break;
                        default:
                            x += +res[i][len - 2];
                            y += +res[i][len - 1];
                    }
                }
                res.toString = R._path2string;
                pth.rel = pathClone(res);
                return res;
            },
            pathToAbsolute = R._pathToAbsolute = function (pathArray) {
                var pth = paths(pathArray);
                if (pth.abs) {
                    return pathClone(pth.abs);
                }
                if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
                    pathArray = R.parsePathString(pathArray);
                }
                if (!pathArray || !pathArray.length) {
                    return [["M", 0, 0]];
                }
                var res = [],
                    x = 0,
                    y = 0,
                    mx = 0,
                    my = 0,
                    start = 0;
                if (pathArray[0][0] == "M") {
                    x = +pathArray[0][1];
                    y = +pathArray[0][2];
                    mx = x;
                    my = y;
                    start++;
                    res[0] = ["M", x, y];
                }
                var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
                for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
                    res.push(r = []);
                    pa = pathArray[i];
                    if (pa[0] != upperCase.call(pa[0])) {
                        r[0] = upperCase.call(pa[0]);
                        switch (r[0]) {
                            case "A":
                                r[1] = pa[1];
                                r[2] = pa[2];
                                r[3] = pa[3];
                                r[4] = pa[4];
                                r[5] = pa[5];
                                r[6] = +(pa[6] + x);
                                r[7] = +(pa[7] + y);
                                break;
                            case "V":
                                r[1] = +pa[1] + y;
                                break;
                            case "H":
                                r[1] = +pa[1] + x;
                                break;
                            case "R":
                                var dots = [x, y][concat](pa.slice(1));
                                for (var j = 2, jj = dots.length; j < jj; j++) {
                                    dots[j] = +dots[j] + x;
                                    dots[++j] = +dots[j] + y;
                                }
                                res.pop();
                                res = res[concat](catmullRom2bezier(dots, crz));
                                break;
                            case "M":
                                mx = +pa[1] + x;
                                my = +pa[2] + y;
                            default:
                                for (j = 1, jj = pa.length; j < jj; j++) {
                                    r[j] = +pa[j] + ((j % 2) ? x : y);
                                }
                        }
                    } else if (pa[0] == "R") {
                        dots = [x, y][concat](pa.slice(1));
                        res.pop();
                        res = res[concat](catmullRom2bezier(dots, crz));
                        r = ["R"][concat](pa.slice(-2));
                    } else {
                        for (var k = 0, kk = pa.length; k < kk; k++) {
                            r[k] = pa[k];
                        }
                    }
                    switch (r[0]) {
                        case "Z":
                            x = mx;
                            y = my;
                            break;
                        case "H":
                            x = r[1];
                            break;
                        case "V":
                            y = r[1];
                            break;
                        case "M":
                            mx = r[r.length - 2];
                            my = r[r.length - 1];
                        default:
                            x = r[r.length - 2];
                            y = r[r.length - 1];
                    }
                }
                res.toString = R._path2string;
                pth.abs = pathClone(res);
                return res;
            },
            l2c = function (x1, y1, x2, y2) {
                return [x1, y1, x2, y2, x2, y2];
            },
            q2c = function (x1, y1, ax, ay, x2, y2) {
                var _13 = 1 / 3,
                    _23 = 2 / 3;
                return [
                        _13 * x1 + _23 * ax,
                        _13 * y1 + _23 * ay,
                        _13 * x2 + _23 * ax,
                        _13 * y2 + _23 * ay,
                        x2,
                        y2
                    ];
            },
            a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
                // for more information of where this math came from visit:
                // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
                var _120 = PI * 120 / 180,
                    rad = PI / 180 * (+angle || 0),
                    res = [],
                    xy,
                    rotate = cacher(function (x, y, rad) {
                        var X = x * math.cos(rad) - y * math.sin(rad),
                            Y = x * math.sin(rad) + y * math.cos(rad);
                        return {x: X, y: Y};
                    });
                if (!recursive) {
                    xy = rotate(x1, y1, -rad);
                    x1 = xy.x;
                    y1 = xy.y;
                    xy = rotate(x2, y2, -rad);
                    x2 = xy.x;
                    y2 = xy.y;
                    var cos = math.cos(PI / 180 * angle),
                        sin = math.sin(PI / 180 * angle),
                        x = (x1 - x2) / 2,
                        y = (y1 - y2) / 2;
                    var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
                    if (h > 1) {
                        h = math.sqrt(h);
                        rx = h * rx;
                        ry = h * ry;
                    }
                    var rx2 = rx * rx,
                        ry2 = ry * ry,
                        k = (large_arc_flag == sweep_flag ? -1 : 1) *
                            math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
                        cx = k * rx * y / ry + (x1 + x2) / 2,
                        cy = k * -ry * x / rx + (y1 + y2) / 2,
                        f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
                        f2 = math.asin(((y2 - cy) / ry).toFixed(9));
    
                    f1 = x1 < cx ? PI - f1 : f1;
                    f2 = x2 < cx ? PI - f2 : f2;
                    f1 < 0 && (f1 = PI * 2 + f1);
                    f2 < 0 && (f2 = PI * 2 + f2);
                    if (sweep_flag && f1 > f2) {
                        f1 = f1 - PI * 2;
                    }
                    if (!sweep_flag && f2 > f1) {
                        f2 = f2 - PI * 2;
                    }
                } else {
                    f1 = recursive[0];
                    f2 = recursive[1];
                    cx = recursive[2];
                    cy = recursive[3];
                }
                var df = f2 - f1;
                if (abs(df) > _120) {
                    var f2old = f2,
                        x2old = x2,
                        y2old = y2;
                    f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
                    x2 = cx + rx * math.cos(f2);
                    y2 = cy + ry * math.sin(f2);
                    res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
                }
                df = f2 - f1;
                var c1 = math.cos(f1),
                    s1 = math.sin(f1),
                    c2 = math.cos(f2),
                    s2 = math.sin(f2),
                    t = math.tan(df / 4),
                    hx = 4 / 3 * rx * t,
                    hy = 4 / 3 * ry * t,
                    m1 = [x1, y1],
                    m2 = [x1 + hx * s1, y1 - hy * c1],
                    m3 = [x2 + hx * s2, y2 - hy * c2],
                    m4 = [x2, y2];
                m2[0] = 2 * m1[0] - m2[0];
                m2[1] = 2 * m1[1] - m2[1];
                if (recursive) {
                    return [m2, m3, m4][concat](res);
                } else {
                    res = [m2, m3, m4][concat](res).join()[split](",");
                    var newres = [];
                    for (var i = 0, ii = res.length; i < ii; i++) {
                        newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
                    }
                    return newres;
                }
            },
            findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
                var t1 = 1 - t;
                return {
                    x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
                    y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
                };
            },
            curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
                var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
                    b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
                    c = p1x - c1x,
                    t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
                    t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
                    y = [p1y, p2y],
                    x = [p1x, p2x],
                    dot;
                abs(t1) > "1e12" && (t1 = .5);
                abs(t2) > "1e12" && (t2 = .5);
                if (t1 > 0 && t1 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                if (t2 > 0 && t2 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
                b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
                c = p1y - c1y;
                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
                abs(t1) > "1e12" && (t1 = .5);
                abs(t2) > "1e12" && (t2 = .5);
                if (t1 > 0 && t1 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                if (t2 > 0 && t2 < 1) {
                    dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
                    x.push(dot.x);
                    y.push(dot.y);
                }
                return {
                    min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
                    max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
                };
            }),
            path2curve = R._path2curve = cacher(function (path, path2) {
                var pth = !path2 && paths(path);
                if (!path2 && pth.curve) {
                    return pathClone(pth.curve);
                }
                var p = pathToAbsolute(path),
                    p2 = path2 && pathToAbsolute(path2),
                    attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
                    attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
                    processPath = function (path, d, pcom) {
                        var nx, ny;
                        if (!path) {
                            return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
                        }
                        !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
                        switch (path[0]) {
                            case "M":
                                d.X = path[1];
                                d.Y = path[2];
                                break;
                            case "A":
                                path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
                                break;
                            case "S":
                                if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
                                    nx = d.x * 2 - d.bx;          // And reflect the previous
                                    ny = d.y * 2 - d.by;          // command's control point relative to the current point.
                                }
                                else {                            // or some else or nothing
                                    nx = d.x;
                                    ny = d.y;
                                }
                                path = ["C", nx, ny][concat](path.slice(1));
                                break;
                            case "T":
                                if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
                                    d.qx = d.x * 2 - d.qx;        // And make a reflection similar
                                    d.qy = d.y * 2 - d.qy;        // to case "S".
                                }
                                else {                            // or something else or nothing
                                    d.qx = d.x;
                                    d.qy = d.y;
                                }
                                path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
                                break;
                            case "Q":
                                d.qx = path[1];
                                d.qy = path[2];
                                path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
                                break;
                            case "L":
                                path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
                                break;
                            case "H":
                                path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
                                break;
                            case "V":
                                path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
                                break;
                            case "Z":
                                path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
                                break;
                        }
                        return path;
                    },
                    fixArc = function (pp, i) {
                        if (pp[i].length > 7) {
                            pp[i].shift();
                            var pi = pp[i];
                            while (pi.length) {
                                pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
                            }
                            pp.splice(i, 1);
                            ii = mmax(p.length, p2 && p2.length || 0);
                        }
                    },
                    fixM = function (path1, path2, a1, a2, i) {
                        if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
                            path2.splice(i, 0, ["M", a2.x, a2.y]);
                            a1.bx = 0;
                            a1.by = 0;
                            a1.x = path1[i][1];
                            a1.y = path1[i][2];
                            ii = mmax(p.length, p2 && p2.length || 0);
                        }
                    };
                for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
                    p[i] = processPath(p[i], attrs);
                    fixArc(p, i);
                    p2 && (p2[i] = processPath(p2[i], attrs2));
                    p2 && fixArc(p2, i);
                    fixM(p, p2, attrs, attrs2, i);
                    fixM(p2, p, attrs2, attrs, i);
                    var seg = p[i],
                        seg2 = p2 && p2[i],
                        seglen = seg.length,
                        seg2len = p2 && seg2.length;
                    attrs.x = seg[seglen - 2];
                    attrs.y = seg[seglen - 1];
                    attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
                    attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
                    attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
                    attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
                    attrs2.x = p2 && seg2[seg2len - 2];
                    attrs2.y = p2 && seg2[seg2len - 1];
                }
                if (!p2) {
                    pth.curve = pathClone(p);
                }
                return p2 ? [p, p2] : p;
            }, null, pathClone),
            parseDots = R._parseDots = cacher(function (gradient) {
                var dots = [];
                for (var i = 0, ii = gradient.length; i < ii; i++) {
                    var dot = {},
                        par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
                    dot.color = R.getRGB(par[1]);
                    if (dot.color.error) {
                        return null;
                    }
                    dot.color = dot.color.hex;
                    par[2] && (dot.offset = par[2] + "%");
                    dots.push(dot);
                }
                for (i = 1, ii = dots.length - 1; i < ii; i++) {
                    if (!dots[i].offset) {
                        var start = toFloat(dots[i - 1].offset || 0),
                            end = 0;
                        for (var j = i + 1; j < ii; j++) {
                            if (dots[j].offset) {
                                end = dots[j].offset;
                                break;
                            }
                        }
                        if (!end) {
                            end = 100;
                            j = ii;
                        }
                        end = toFloat(end);
                        var d = (end - start) / (j - i + 1);
                        for (; i < j; i++) {
                            start += d;
                            dots[i].offset = start + "%";
                        }
                    }
                }
                return dots;
            }),
            tear = R._tear = function (el, paper) {
                el == paper.top && (paper.top = el.prev);
                el == paper.bottom && (paper.bottom = el.next);
                el.next && (el.next.prev = el.prev);
                el.prev && (el.prev.next = el.next);
            },
            tofront = R._tofront = function (el, paper) {
                if (paper.top === el) {
                    return;
                }
                tear(el, paper);
                el.next = null;
                el.prev = paper.top;
                paper.top.next = el;
                paper.top = el;
            },
            toback = R._toback = function (el, paper) {
                if (paper.bottom === el) {
                    return;
                }
                tear(el, paper);
                el.next = paper.bottom;
                el.prev = null;
                paper.bottom.prev = el;
                paper.bottom = el;
            },
            insertafter = R._insertafter = function (el, el2, paper) {
                tear(el, paper);
                el2 == paper.top && (paper.top = el);
                el2.next && (el2.next.prev = el);
                el.next = el2.next;
                el.prev = el2;
                el2.next = el;
            },
            insertbefore = R._insertbefore = function (el, el2, paper) {
                tear(el, paper);
                el2 == paper.bottom && (paper.bottom = el);
                el2.prev && (el2.prev.next = el);
                el.prev = el2.prev;
                el2.prev = el;
                el.next = el2;
            },
            /*\
             * Raphael.toMatrix
             [ method ]
             **
             * Utility method
             **
             * Returns matrix of transformations applied to a given path
             > Parameters
             - path (string) path string
             - transform (string|array) transformation string
             = (object) @Matrix
            \*/
            toMatrix = R.toMatrix = function (path, transform) {
                var bb = pathDimensions(path),
                    el = {
                        _: {
                            transform: E
                        },
                        getBBox: function () {
                            return bb;
                        }
                    };
                extractTransform(el, transform);
                return el.matrix;
            },
            /*\
             * Raphael.transformPath
             [ method ]
             **
             * Utility method
             **
             * Returns path transformed by a given transformation
             > Parameters
             - path (string) path string
             - transform (string|array) transformation string
             = (string) path
            \*/
            transformPath = R.transformPath = function (path, transform) {
                return mapPath(path, toMatrix(path, transform));
            },
            extractTransform = R._extractTransform = function (el, tstr) {
                if (tstr == null) {
                    return el._.transform;
                }
                tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
                var tdata = R.parseTransformString(tstr),
                    deg = 0,
                    dx = 0,
                    dy = 0,
                    sx = 1,
                    sy = 1,
                    _ = el._,
                    m = new Matrix;
                _.transform = tdata || [];
                if (tdata) {
                    for (var i = 0, ii = tdata.length; i < ii; i++) {
                        var t = tdata[i],
                            tlen = t.length,
                            command = Str(t[0]).toLowerCase(),
                            absolute = t[0] != command,
                            inver = absolute ? m.invert() : 0,
                            x1,
                            y1,
                            x2,
                            y2,
                            bb;
                        if (command == "t" && tlen == 3) {
                            if (absolute) {
                                x1 = inver.x(0, 0);
                                y1 = inver.y(0, 0);
                                x2 = inver.x(t[1], t[2]);
                                y2 = inver.y(t[1], t[2]);
                                m.translate(x2 - x1, y2 - y1);
                            } else {
                                m.translate(t[1], t[2]);
                            }
                        } else if (command == "r") {
                            if (tlen == 2) {
                                bb = bb || el.getBBox(1);
                                m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                                deg += t[1];
                            } else if (tlen == 4) {
                                if (absolute) {
                                    x2 = inver.x(t[2], t[3]);
                                    y2 = inver.y(t[2], t[3]);
                                    m.rotate(t[1], x2, y2);
                                } else {
                                    m.rotate(t[1], t[2], t[3]);
                                }
                                deg += t[1];
                            }
                        } else if (command == "s") {
                            if (tlen == 2 || tlen == 3) {
                                bb = bb || el.getBBox(1);
                                m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                                sx *= t[1];
                                sy *= t[tlen - 1];
                            } else if (tlen == 5) {
                                if (absolute) {
                                    x2 = inver.x(t[3], t[4]);
                                    y2 = inver.y(t[3], t[4]);
                                    m.scale(t[1], t[2], x2, y2);
                                } else {
                                    m.scale(t[1], t[2], t[3], t[4]);
                                }
                                sx *= t[1];
                                sy *= t[2];
                            }
                        } else if (command == "m" && tlen == 7) {
                            m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
                        }
                        _.dirtyT = 1;
                        el.matrix = m;
                    }
                }
    
                /*\
                 * Element.matrix
                 [ property (object) ]
                 **
                 * Keeps @Matrix object, which represents element transformation
                \*/
                el.matrix = m;
    
                _.sx = sx;
                _.sy = sy;
                _.deg = deg;
                _.dx = dx = m.e;
                _.dy = dy = m.f;
    
                if (sx == 1 && sy == 1 && !deg && _.bbox) {
                    _.bbox.x += +dx;
                    _.bbox.y += +dy;
                } else {
                    _.dirtyT = 1;
                }
            },
            getEmpty = function (item) {
                var l = item[0];
                switch (l.toLowerCase()) {
                    case "t": return [l, 0, 0];
                    case "m": return [l, 1, 0, 0, 1, 0, 0];
                    case "r": if (item.length == 4) {
                        return [l, 0, item[2], item[3]];
                    } else {
                        return [l, 0];
                    }
                    case "s": if (item.length == 5) {
                        return [l, 1, 1, item[3], item[4]];
                    } else if (item.length == 3) {
                        return [l, 1, 1];
                    } else {
                        return [l, 1];
                    }
                }
            },
            equaliseTransform = R._equaliseTransform = function (t1, t2) {
                t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
                t1 = R.parseTransformString(t1) || [];
                t2 = R.parseTransformString(t2) || [];
                var maxlength = mmax(t1.length, t2.length),
                    from = [],
                    to = [],
                    i = 0, j, jj,
                    tt1, tt2;
                for (; i < maxlength; i++) {
                    tt1 = t1[i] || getEmpty(t2[i]);
                    tt2 = t2[i] || getEmpty(tt1);
                    if ((tt1[0] != tt2[0]) ||
                        (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
                        (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
                        ) {
                        return;
                    }
                    from[i] = [];
                    to[i] = [];
                    for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
                        j in tt1 && (from[i][j] = tt1[j]);
                        j in tt2 && (to[i][j] = tt2[j]);
                    }
                }
                return {
                    from: from,
                    to: to
                };
            };
        R._getContainer = function (x, y, w, h) {
            var container;
            container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
            if (container == null) {
                return;
            }
            if (container.tagName) {
                if (y == null) {
                    return {
                        container: container,
                        width: container.style.pixelWidth || container.offsetWidth,
                        height: container.style.pixelHeight || container.offsetHeight
                    };
                } else {
                    return {
                        container: container,
                        width: y,
                        height: w
                    };
                }
            }
            return {
                container: 1,
                x: x,
                y: y,
                width: w,
                height: h
            };
        };
        /*\
         * Raphael.pathToRelative
         [ method ]
         **
         * Utility method
         **
         * Converts path to relative form
         > Parameters
         - pathString (string|array) path string or array of segments
         = (array) array of segments.
        \*/
        R.pathToRelative = pathToRelative;
        R._engine = {};
        /*\
         * Raphael.path2curve
         [ method ]
         **
         * Utility method
         **
         * Converts path to a new path where all segments are cubic bezier curves.
         > Parameters
         - pathString (string|array) path string or array of segments
         = (array) array of segments.
        \*/
        R.path2curve = path2curve;
        /*\
         * Raphael.matrix
         [ method ]
         **
         * Utility method
         **
         * Returns matrix based on given parameters.
         > Parameters
         - a (number)
         - b (number)
         - c (number)
         - d (number)
         - e (number)
         - f (number)
         = (object) @Matrix
        \*/
        R.matrix = function (a, b, c, d, e, f) {
            return new Matrix(a, b, c, d, e, f);
        };
        function Matrix(a, b, c, d, e, f) {
            if (a != null) {
                this.a = +a;
                this.b = +b;
                this.c = +c;
                this.d = +d;
                this.e = +e;
                this.f = +f;
            } else {
                this.a = 1;
                this.b = 0;
                this.c = 0;
                this.d = 1;
                this.e = 0;
                this.f = 0;
            }
        }
        (function (matrixproto) {
            /*\
             * Matrix.add
             [ method ]
             **
             * Adds given matrix to existing one.
             > Parameters
             - a (number)
             - b (number)
             - c (number)
             - d (number)
             - e (number)
             - f (number)
             or
             - matrix (object) @Matrix
            \*/
            matrixproto.add = function (a, b, c, d, e, f) {
                var out = [[], [], []],
                    m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
                    matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
                    x, y, z, res;
    
                if (a && a instanceof Matrix) {
                    matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
                }
    
                for (x = 0; x < 3; x++) {
                    for (y = 0; y < 3; y++) {
                        res = 0;
                        for (z = 0; z < 3; z++) {
                            res += m[x][z] * matrix[z][y];
                        }
                        out[x][y] = res;
                    }
                }
                this.a = out[0][0];
                this.b = out[1][0];
                this.c = out[0][1];
                this.d = out[1][1];
                this.e = out[0][2];
                this.f = out[1][2];
            };
            /*\
             * Matrix.invert
             [ method ]
             **
             * Returns inverted version of the matrix
             = (object) @Matrix
            \*/
            matrixproto.invert = function () {
                var me = this,
                    x = me.a * me.d - me.b * me.c;
                return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
            };
            /*\
             * Matrix.clone
             [ method ]
             **
             * Returns copy of the matrix
             = (object) @Matrix
            \*/
            matrixproto.clone = function () {
                return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
            };
            /*\
             * Matrix.translate
             [ method ]
             **
             * Translate the matrix
             > Parameters
             - x (number)
             - y (number)
            \*/
            matrixproto.translate = function (x, y) {
                this.add(1, 0, 0, 1, x, y);
            };
            /*\
             * Matrix.scale
             [ method ]
             **
             * Scales the matrix
             > Parameters
             - x (number)
             - y (number) #optional
             - cx (number) #optional
             - cy (number) #optional
            \*/
            matrixproto.scale = function (x, y, cx, cy) {
                y == null && (y = x);
                (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
                this.add(x, 0, 0, y, 0, 0);
                (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
            };
            /*\
             * Matrix.rotate
             [ method ]
             **
             * Rotates the matrix
             > Parameters
             - a (number)
             - x (number)
             - y (number)
            \*/
            matrixproto.rotate = function (a, x, y) {
                a = R.rad(a);
                x = x || 0;
                y = y || 0;
                var cos = +math.cos(a).toFixed(9),
                    sin = +math.sin(a).toFixed(9);
                this.add(cos, sin, -sin, cos, x, y);
                this.add(1, 0, 0, 1, -x, -y);
            };
            /*\
             * Matrix.x
             [ method ]
             **
             * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
             > Parameters
             - x (number)
             - y (number)
             = (number) x
            \*/
            matrixproto.x = function (x, y) {
                return x * this.a + y * this.c + this.e;
            };
            /*\
             * Matrix.y
             [ method ]
             **
             * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
             > Parameters
             - x (number)
             - y (number)
             = (number) y
            \*/
            matrixproto.y = function (x, y) {
                return x * this.b + y * this.d + this.f;
            };
            matrixproto.get = function (i) {
                return +this[Str.fromCharCode(97 + i)].toFixed(4);
            };
            matrixproto.toString = function () {
                return R.svg ?
                    "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
                    [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
            };
            matrixproto.toFilter = function () {
                return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
                    ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
                    ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
            };
            matrixproto.offset = function () {
                return [this.e.toFixed(4), this.f.toFixed(4)];
            };
            function norm(a) {
                return a[0] * a[0] + a[1] * a[1];
            }
            function normalize(a) {
                var mag = math.sqrt(norm(a));
                a[0] && (a[0] /= mag);
                a[1] && (a[1] /= mag);
            }
            /*\
             * Matrix.split
             [ method ]
             **
             * Splits matrix into primitive transformations
             = (object) in format:
             o dx (number) translation by x
             o dy (number) translation by y
             o scalex (number) scale by x
             o scaley (number) scale by y
             o shear (number) shear
             o rotate (number) rotation in deg
             o isSimple (boolean) could it be represented via simple transformations
            \*/
            matrixproto.split = function () {
                var out = {};
                // translation
                out.dx = this.e;
                out.dy = this.f;
    
                // scale and shear
                var row = [[this.a, this.c], [this.b, this.d]];
                out.scalex = math.sqrt(norm(row[0]));
                normalize(row[0]);
    
                out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
                row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
    
                out.scaley = math.sqrt(norm(row[1]));
                normalize(row[1]);
                out.shear /= out.scaley;
    
                // rotation
                var sin = -row[0][1],
                    cos = row[1][1];
                if (cos < 0) {
                    out.rotate = R.deg(math.acos(cos));
                    if (sin < 0) {
                        out.rotate = 360 - out.rotate;
                    }
                } else {
                    out.rotate = R.deg(math.asin(sin));
                }
    
                out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
                out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
                out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
                return out;
            };
            /*\
             * Matrix.toTransformString
             [ method ]
             **
             * Return transform string that represents given matrix
             = (string) transform string
            \*/
            matrixproto.toTransformString = function (shorter) {
                var s = shorter || this[split]();
                if (s.isSimple) {
                    s.scalex = +s.scalex.toFixed(4);
                    s.scaley = +s.scaley.toFixed(4);
                    s.rotate = +s.rotate.toFixed(4);
                    return  (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) +
                            (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
                            (s.rotate ? "r" + [s.rotate, 0, 0] : E);
                } else {
                    return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
                }
            };
        })(Matrix.prototype);
    
        // WebKit rendering bug workaround method
        var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
        if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
            (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
            /*\
             * Paper.safari
             [ method ]
             **
             * There is an inconvenient rendering bug in Safari (WebKit):
             * sometimes the rendering should be forced.
             * This method should help with dealing with this bug.
            \*/
            paperproto.safari = function () {
                var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
                setTimeout(function () {rect.remove();});
            };
        } else {
            paperproto.safari = fun;
        }
    
        var preventDefault = function () {
            this.returnValue = false;
        },
        preventTouch = function () {
            return this.originalEvent.preventDefault();
        },
        stopPropagation = function () {
            this.cancelBubble = true;
        },
        stopTouch = function () {
            return this.originalEvent.stopPropagation();
        },
        getEventPosition = function (e) {
            var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
    
            return {
                x: e.clientX + scrollX,
                y: e.clientY + scrollY
            };
        },
        addEvent = (function () {
            if (g.doc.addEventListener) {
                return function (obj, type, fn, element) {
                    var f = function (e) {
                        var pos = getEventPosition(e);
                        return fn.call(element, e, pos.x, pos.y);
                    };
                    obj.addEventListener(type, f, false);
    
                    if (supportsTouch && touchMap[type]) {
                        var _f = function (e) {
                            var pos = getEventPosition(e),
                                olde = e;
    
                            for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
                                if (e.targetTouches[i].target == obj) {
                                    e = e.targetTouches[i];
                                    e.originalEvent = olde;
                                    e.preventDefault = preventTouch;
                                    e.stopPropagation = stopTouch;
                                    break;
                                }
                            }
    
                            return fn.call(element, e, pos.x, pos.y);
                        };
                        obj.addEventListener(touchMap[type], _f, false);
                    }
    
                    return function () {
                        obj.removeEventListener(type, f, false);
    
                        if (supportsTouch && touchMap[type])
                            obj.removeEventListener(touchMap[type], f, false);
    
                        return true;
                    };
                };
            } else if (g.doc.attachEvent) {
                return function (obj, type, fn, element) {
                    var f = function (e) {
                        e = e || g.win.event;
                        var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
                            x = e.clientX + scrollX,
                            y = e.clientY + scrollY;
                        e.preventDefault = e.preventDefault || preventDefault;
                        e.stopPropagation = e.stopPropagation || stopPropagation;
                        return fn.call(element, e, x, y);
                    };
                    obj.attachEvent("on" + type, f);
                    var detacher = function () {
                        obj.detachEvent("on" + type, f);
                        return true;
                    };
                    return detacher;
                };
            }
        })(),
        drag = [],
        dragMove = function (e) {
            var x = e.clientX,
                y = e.clientY,
                scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
                dragi,
                j = drag.length;
            while (j--) {
                dragi = drag[j];
                if (supportsTouch && e.touches) {
                    var i = e.touches.length,
                        touch;
                    while (i--) {
                        touch = e.touches[i];
                        if (touch.identifier == dragi.el._drag.id) {
                            x = touch.clientX;
                            y = touch.clientY;
                            (e.originalEvent ? e.originalEvent : e).preventDefault();
                            break;
                        }
                    }
                } else {
                    e.preventDefault();
                }
                var node = dragi.el.node,
                    o,
                    next = node.nextSibling,
                    parent = node.parentNode,
                    display = node.style.display;
                g.win.opera && parent.removeChild(node);
                node.style.display = "none";
                o = dragi.el.paper.getElementByPoint(x, y);
                node.style.display = display;
                g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
                o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
                x += scrollX;
                y += scrollY;
                eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
            }
        },
        dragUp = function (e) {
            R.unmousemove(dragMove).unmouseup(dragUp);
            var i = drag.length,
                dragi;
            while (i--) {
                dragi = drag[i];
                dragi.el._drag = {};
                eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
            }
            drag = [];
        },
        /*\
         * Raphael.el
         [ property (object) ]
         **
         * You can add your own method to elements. This is usefull when you want to hack default functionality or
         * want to wrap some common transformation or attributes in one method. In difference to canvas methods,
         * you can redefine element method at any time. Expending element methods wouldn’t affect set.
         > Usage
         | Raphael.el.red = function () {
         |     this.attr({fill: "#f00"});
         | };
         | // then use it
         | paper.circle(100, 100, 20).red();
        \*/
        elproto = R.el = {};
        /*\
         * Element.click
         [ method ]
         **
         * Adds event handler for click for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unclick
         [ method ]
         **
         * Removes event handler for click for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.dblclick
         [ method ]
         **
         * Adds event handler for double click for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.undblclick
         [ method ]
         **
         * Removes event handler for double click for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mousedown
         [ method ]
         **
         * Adds event handler for mousedown for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmousedown
         [ method ]
         **
         * Removes event handler for mousedown for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mousemove
         [ method ]
         **
         * Adds event handler for mousemove for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmousemove
         [ method ]
         **
         * Removes event handler for mousemove for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseout
         [ method ]
         **
         * Adds event handler for mouseout for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseout
         [ method ]
         **
         * Removes event handler for mouseout for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseover
         [ method ]
         **
         * Adds event handler for mouseover for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseover
         [ method ]
         **
         * Removes event handler for mouseover for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.mouseup
         [ method ]
         **
         * Adds event handler for mouseup for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.unmouseup
         [ method ]
         **
         * Removes event handler for mouseup for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchstart
         [ method ]
         **
         * Adds event handler for touchstart for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchstart
         [ method ]
         **
         * Removes event handler for touchstart for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchmove
         [ method ]
         **
         * Adds event handler for touchmove for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchmove
         [ method ]
         **
         * Removes event handler for touchmove for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchend
         [ method ]
         **
         * Adds event handler for touchend for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchend
         [ method ]
         **
         * Removes event handler for touchend for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
    
        /*\
         * Element.touchcancel
         [ method ]
         **
         * Adds event handler for touchcancel for the element.
         > Parameters
         - handler (function) handler for the event
         = (object) @Element
        \*/
        /*\
         * Element.untouchcancel
         [ method ]
         **
         * Removes event handler for touchcancel for the element.
         > Parameters
         - handler (function) #optional handler for the event
         = (object) @Element
        \*/
        for (var i = events.length; i--;) {
            (function (eventName) {
                R[eventName] = elproto[eventName] = function (fn, scope) {
                    if (R.is(fn, "function")) {
                        this.events = this.events || [];
                        this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
                    }
                    return this;
                };
                R["un" + eventName] = elproto["un" + eventName] = function (fn) {
                    var events = this.events || [],
                        l = events.length;
                    while (l--){
                        if (events[l].name == eventName && (R.is(fn, "undefined") || events[l].f == fn)) {
                            events[l].unbind();
                            events.splice(l, 1);
                            !events.length && delete this.events;
                        }
                    }
                    return this;
                };
            })(events[i]);
        }
    
        /*\
         * Element.data
         [ method ]
         **
         * Adds or retrieves given value asociated with given key.
         ** 
         * See also @Element.removeData
         > Parameters
         - key (string) key to store data
         - value (any) #optional value to store
         = (object) @Element
         * or, if value is not specified:
         = (any) value
         * or, if key and value are not specified:
         = (object) Key/value pairs for all the data associated with the element.
         > Usage
         | for (var i = 0, i < 5, i++) {
         |     paper.circle(10 + 15 * i, 10, 10)
         |          .attr({fill: "#000"})
         |          .data("i", i)
         |          .click(function () {
         |             alert(this.data("i"));
         |          });
         | }
        \*/
        elproto.data = function (key, value) {
            var data = eldata[this.id] = eldata[this.id] || {};
            if (arguments.length == 0) {
                return data;
            }
            if (arguments.length == 1) {
                if (R.is(key, "object")) {
                    for (var i in key) if (key[has](i)) {
                        this.data(i, key[i]);
                    }
                    return this;
                }
                eve("raphael.data.get." + this.id, this, data[key], key);
                return data[key];
            }
            data[key] = value;
            eve("raphael.data.set." + this.id, this, value, key);
            return this;
        };
        /*\
         * Element.removeData
         [ method ]
         **
         * Removes value associated with an element by given key.
         * If key is not provided, removes all the data of the element.
         > Parameters
         - key (string) #optional key
         = (object) @Element
        \*/
        elproto.removeData = function (key) {
            if (key == null) {
                eldata[this.id] = {};
            } else {
                eldata[this.id] && delete eldata[this.id][key];
            }
            return this;
        };
         /*\
         * Element.getData
         [ method ]
         **
         * Retrieves the element data
         = (object) data
        \*/
        elproto.getData = function () {
            return clone(eldata[this.id] || {});
        };
        /*\
         * Element.hover
         [ method ]
         **
         * Adds event handlers for hover for the element.
         > Parameters
         - f_in (function) handler for hover in
         - f_out (function) handler for hover out
         - icontext (object) #optional context for hover in handler
         - ocontext (object) #optional context for hover out handler
         = (object) @Element
        \*/
        elproto.hover = function (f_in, f_out, scope_in, scope_out) {
            return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
        };
        /*\
         * Element.unhover
         [ method ]
         **
         * Removes event handlers for hover for the element.
         > Parameters
         - f_in (function) handler for hover in
         - f_out (function) handler for hover out
         = (object) @Element
        \*/
        elproto.unhover = function (f_in, f_out) {
            return this.unmouseover(f_in).unmouseout(f_out);
        };
        var draggable = [];
        /*\
         * Element.drag
         [ method ]
         **
         * Adds event handlers for drag of the element.
         > Parameters
         - onmove (function) handler for moving
         - onstart (function) handler for drag start
         - onend (function) handler for drag end
         - mcontext (object) #optional context for moving handler
         - scontext (object) #optional context for drag start handler
         - econtext (object) #optional context for drag end handler
         * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start, 
         * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element 
         * `drag.over.<id>` will be fired as well.
         *
         * Start event and start handler will be called in specified context or in context of the element with following parameters:
         o x (number) x position of the mouse
         o y (number) y position of the mouse
         o event (object) DOM event object
         * Move event and move handler will be called in specified context or in context of the element with following parameters:
         o dx (number) shift by x from the start point
         o dy (number) shift by y from the start point
         o x (number) x position of the mouse
         o y (number) y position of the mouse
         o event (object) DOM event object
         * End event and end handler will be called in specified context or in context of the element with following parameters:
         o event (object) DOM event object
         = (object) @Element
        \*/
        elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
            function start(e) {
                (e.originalEvent || e).preventDefault();
                var x = e.clientX,
                    y = e.clientY,
                    scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
                    scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
                this._drag.id = e.identifier;
                if (supportsTouch && e.touches) {
                    var i = e.touches.length, touch;
                    while (i--) {
                        touch = e.touches[i];
                        this._drag.id = touch.identifier;
                        if (touch.identifier == this._drag.id) {
                            x = touch.clientX;
                            y = touch.clientY;
                            break;
                        }
                    }
                }
                this._drag.x = x + scrollX;
                this._drag.y = y + scrollY;
                !drag.length && R.mousemove(dragMove).mouseup(dragUp);
                drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
                onstart && eve.on("raphael.drag.start." + this.id, onstart);
                onmove && eve.on("raphael.drag.move." + this.id, onmove);
                onend && eve.on("raphael.drag.end." + this.id, onend);
                eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
            }
            this._drag = {};
            draggable.push({el: this, start: start});
            this.mousedown(start);
            return this;
        };
        /*\
         * Element.onDragOver
         [ method ]
         **
         * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
         > Parameters
         - f (function) handler for event, first argument would be the element you are dragging over
        \*/
        elproto.onDragOver = function (f) {
            f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
        };
        /*\
         * Element.undrag
         [ method ]
         **
         * Removes all drag event handlers from given element.
        \*/
        elproto.undrag = function () {
            var i = draggable.length;
            while (i--) if (draggable[i].el == this) {
                this.unmousedown(draggable[i].start);
                draggable.splice(i, 1);
                eve.unbind("raphael.drag.*." + this.id);
            }
            !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
            drag = [];
        };
        /*\
         * Paper.circle
         [ method ]
         **
         * Draws a circle.
         **
         > Parameters
         **
         - x (number) x coordinate of the centre
         - y (number) y coordinate of the centre
         - r (number) radius
         = (object) Raphaël element object with type “circle”
         **
         > Usage
         | var c = paper.circle(50, 50, 40);
        \*/
        paperproto.circle = function (x, y, r) {
            var out = R._engine.circle(this, x || 0, y || 0, r || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.rect
         [ method ]
         *
         * Draws a rectangle.
         **
         > Parameters
         **
         - x (number) x coordinate of the top left corner
         - y (number) y coordinate of the top left corner
         - width (number) width
         - height (number) height
         - r (number) #optional radius for rounded corners, default is 0
         = (object) Raphaël element object with type “rect”
         **
         > Usage
         | // regular rectangle
         | var c = paper.rect(10, 10, 50, 50);
         | // rectangle with rounded corners
         | var c = paper.rect(40, 40, 50, 50, 10);
        \*/
        paperproto.rect = function (x, y, w, h, r) {
            var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.ellipse
         [ method ]
         **
         * Draws an ellipse.
         **
         > Parameters
         **
         - x (number) x coordinate of the centre
         - y (number) y coordinate of the centre
         - rx (number) horizontal radius
         - ry (number) vertical radius
         = (object) Raphaël element object with type “ellipse”
         **
         > Usage
         | var c = paper.ellipse(50, 50, 40, 20);
        \*/
        paperproto.ellipse = function (x, y, rx, ry) {
            var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.path
         [ method ]
         **
         * Creates a path element by given path data string.
         > Parameters
         - pathString (string) #optional path string in SVG format.
         * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
         | "M10,20L30,40"
         * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
         *
         # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p>
         # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
         # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
         # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
         # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
         # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
         # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
         # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
         # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
         # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
         # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
         # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
         # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
         * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier.
         * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
         > Usage
         | var c = paper.path("M10 10L90 90");
         | // draw a diagonal line:
         | // move to 10,10, line to 90,90
         * For example of path strings, check out these icons: http://raphaeljs.com/icons/
        \*/
        paperproto.path = function (pathString) {
            pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
            var out = R._engine.path(R.format[apply](R, arguments), this);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.image
         [ method ]
         **
         * Embeds an image into the surface.
         **
         > Parameters
         **
         - src (string) URI of the source image
         - x (number) x coordinate position
         - y (number) y coordinate position
         - width (number) width of the image
         - height (number) height of the image
         = (object) Raphaël element object with type “image”
         **
         > Usage
         | var c = paper.image("apple.png", 10, 10, 80, 80);
        \*/
        paperproto.image = function (src, x, y, w, h) {
            var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.text
         [ method ]
         **
         * Draws a text string. If you need line breaks, put “\n” in the string.
         **
         > Parameters
         **
         - x (number) x coordinate position
         - y (number) y coordinate position
         - text (string) The text string to draw
         = (object) Raphaël element object with type “text”
         **
         > Usage
         | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!");
        \*/
        paperproto.text = function (x, y, text) {
            var out = R._engine.text(this, x || 0, y || 0, Str(text));
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Paper.set
         [ method ]
         **
         * Creates array-like object to keep and operate several elements at once.
         * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements.
         * Sets act as pseudo elements — all methods available to an element can be used on a set.
         = (object) array-like object that represents set of elements
         **
         > Usage
         | var st = paper.set();
         | st.push(
         |     paper.circle(10, 10, 5),
         |     paper.circle(30, 10, 5)
         | );
         | st.attr({fill: "red"}); // changes the fill of both circles
        \*/
        paperproto.set = function (itemsArray) {
            !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
            var out = new Set(itemsArray);
            this.__set__ && this.__set__.push(out);
            out["paper"] = this;
            out["type"] = "set";
            return out;
        };
        /*\
         * Paper.setStart
         [ method ]
         **
         * Creates @Paper.set. All elements that will be created after calling this method and before calling
         * @Paper.setFinish will be added to the set.
         **
         > Usage
         | paper.setStart();
         | paper.circle(10, 10, 5),
         | paper.circle(30, 10, 5)
         | var st = paper.setFinish();
         | st.attr({fill: "red"}); // changes the fill of both circles
        \*/
        paperproto.setStart = function (set) {
            this.__set__ = set || this.set();
        };
        /*\
         * Paper.setFinish
         [ method ]
         **
         * See @Paper.setStart. This method finishes catching and returns resulting set.
         **
         = (object) set
        \*/
        paperproto.setFinish = function (set) {
            var out = this.__set__;
            delete this.__set__;
            return out;
        };
        /*\
         * Paper.setSize
         [ method ]
         **
         * If you need to change dimensions of the canvas call this method
         **
         > Parameters
         **
         - width (number) new width of the canvas
         - height (number) new height of the canvas
        \*/
        paperproto.setSize = function (width, height) {
            return R._engine.setSize.call(this, width, height);
        };
        /*\
         * Paper.setViewBox
         [ method ]
         **
         * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by 
         * specifying new boundaries.
         **
         > Parameters
         **
         - x (number) new x position, default is `0`
         - y (number) new y position, default is `0`
         - w (number) new width of the canvas
         - h (number) new height of the canvas
         - fit (boolean) `true` if you want graphics to fit into new boundary box
        \*/
        paperproto.setViewBox = function (x, y, w, h, fit) {
            return R._engine.setViewBox.call(this, x, y, w, h, fit);
        };
        /*\
         * Paper.top
         [ property ]
         **
         * Points to the topmost element on the paper
        \*/
        /*\
         * Paper.bottom
         [ property ]
         **
         * Points to the bottom element on the paper
        \*/
        paperproto.top = paperproto.bottom = null;
        /*\
         * Paper.raphael
         [ property ]
         **
         * Points to the @Raphael object/function
        \*/
        paperproto.raphael = R;
        var getOffset = function (elem) {
            var box = elem.getBoundingClientRect(),
                doc = elem.ownerDocument,
                body = doc.body,
                docElem = doc.documentElement,
                clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
                top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
                left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
            return {
                y: top,
                x: left
            };
        };
        /*\
         * Paper.getElementByPoint
         [ method ]
         **
         * Returns you topmost element under given point.
         **
         = (object) Raphaël element object
         > Parameters
         **
         - x (number) x coordinate from the top left corner of the window
         - y (number) y coordinate from the top left corner of the window
         > Usage
         | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
        \*/
        paperproto.getElementByPoint = function (x, y) {
            var paper = this,
                svg = paper.canvas,
                target = g.doc.elementFromPoint(x, y);
            if (g.win.opera && target.tagName == "svg") {
                var so = getOffset(svg),
                    sr = svg.createSVGRect();
                sr.x = x - so.x;
                sr.y = y - so.y;
                sr.width = sr.height = 1;
                var hits = svg.getIntersectionList(sr, null);
                if (hits.length) {
                    target = hits[hits.length - 1];
                }
            }
            if (!target) {
                return null;
            }
            while (target.parentNode && target != svg.parentNode && !target.raphael) {
                target = target.parentNode;
            }
            target == paper.canvas.parentNode && (target = svg);
            target = target && target.raphael ? paper.getById(target.raphaelid) : null;
            return target;
        };
    
        /*\
         * Paper.getElementsByBBox
         [ method ]
         **
         * Returns set of elements that have an intersecting bounding box
         **
         > Parameters
         **
         - bbox (object) bbox to check with
         = (object) @Set
         \*/
        paperproto.getElementsByBBox = function (bbox) {
            var set = this.set();
            this.forEach(function (el) {
                if (R.isBBoxIntersect(el.getBBox(), bbox)) {
                    set.push(el);
                }
            });
            return set;
        };
    
        /*\
         * Paper.getById
         [ method ]
         **
         * Returns you element by its internal ID.
         **
         > Parameters
         **
         - id (number) id
         = (object) Raphaël element object
        \*/
        paperproto.getById = function (id) {
            var bot = this.bottom;
            while (bot) {
                if (bot.id == id) {
                    return bot;
                }
                bot = bot.next;
            }
            return null;
        };
        /*\
         * Paper.forEach
         [ method ]
         **
         * Executes given function for each element on the paper
         *
         * If callback function returns `false` it will stop loop running.
         **
         > Parameters
         **
         - callback (function) function to run
         - thisArg (object) context object for the callback
         = (object) Paper object
         > Usage
         | paper.forEach(function (el) {
         |     el.attr({ stroke: "blue" });
         | });
        \*/
        paperproto.forEach = function (callback, thisArg) {
            var bot = this.bottom;
            while (bot) {
                if (callback.call(thisArg, bot) === false) {
                    return this;
                }
                bot = bot.next;
            }
            return this;
        };
        /*\
         * Paper.getElementsByPoint
         [ method ]
         **
         * Returns set of elements that have common point inside
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (object) @Set
        \*/
        paperproto.getElementsByPoint = function (x, y) {
            var set = this.set();
            this.forEach(function (el) {
                if (el.isPointInside(x, y)) {
                    set.push(el);
                }
            });
            return set;
        };
        function x_y() {
            return this.x + S + this.y;
        }
        function x_y_w_h() {
            return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
        }
        /*\
         * Element.isPointInside
         [ method ]
         **
         * Determine if given point is inside this element’s shape
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (boolean) `true` if point inside the shape
        \*/
        elproto.isPointInside = function (x, y) {
            var rp = this.realPath = getPath[this.type](this);
            if (this.attr('transform') && this.attr('transform').length) {
                rp = R.transformPath(rp, this.attr('transform'));
            }
            return R.isPointInsidePath(rp, x, y);
        };
        /*\
         * Element.getBBox
         [ method ]
         **
         * Return bounding box for a given element
         **
         > Parameters
         **
         - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`.
         = (object) Bounding box object:
         o {
         o     x: (number) top left corner x
         o     y: (number) top left corner y
         o     x2: (number) bottom right corner x
         o     y2: (number) bottom right corner y
         o     width: (number) width
         o     height: (number) height
         o }
        \*/
        elproto.getBBox = function (isWithoutTransform) {
            if (this.removed) {
                return {};
            }
            var _ = this._;
            if (isWithoutTransform) {
                if (_.dirty || !_.bboxwt) {
                    this.realPath = getPath[this.type](this);
                    _.bboxwt = pathDimensions(this.realPath);
                    _.bboxwt.toString = x_y_w_h;
                    _.dirty = 0;
                }
                return _.bboxwt;
            }
            if (_.dirty || _.dirtyT || !_.bbox) {
                if (_.dirty || !this.realPath) {
                    _.bboxwt = 0;
                    this.realPath = getPath[this.type](this);
                }
                _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
                _.bbox.toString = x_y_w_h;
                _.dirty = _.dirtyT = 0;
            }
            return _.bbox;
        };
        /*\
         * Element.clone
         [ method ]
         **
         = (object) clone of a given element
         **
        \*/
        elproto.clone = function () {
            if (this.removed) {
                return null;
            }
            var out = this.paper[this.type]().attr(this.attr());
            this.__set__ && this.__set__.push(out);
            return out;
        };
        /*\
         * Element.glow
         [ method ]
         **
         * Return set of elements that create glow-like effect around given element. See @Paper.set.
         *
         * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself.
         **
         > Parameters
         **
         - glow (object) #optional parameters object with all properties optional:
         o {
         o     width (number) size of the glow, default is `10`
         o     fill (boolean) will it be filled, default is `false`
         o     opacity (number) opacity, default is `0.5`
         o     offsetx (number) horizontal offset, default is `0`
         o     offsety (number) vertical offset, default is `0`
         o     color (string) glow colour, default is `black`
         o }
         = (object) @Paper.set of elements that represents glow
        \*/
        elproto.glow = function (glow) {
            if (this.type == "text") {
                return null;
            }
            glow = glow || {};
            var s = {
                width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
                fill: glow.fill || false,
                opacity: glow.opacity || .5,
                offsetx: glow.offsetx || 0,
                offsety: glow.offsety || 0,
                color: glow.color || "#000"
            },
                c = s.width / 2,
                r = this.paper,
                out = r.set(),
                path = this.realPath || getPath[this.type](this);
            path = this.matrix ? mapPath(path, this.matrix) : path;
            for (var i = 1; i < c + 1; i++) {
                out.push(r.path(path).attr({
                    stroke: s.color,
                    fill: s.fill ? s.color : "none",
                    "stroke-linejoin": "round",
                    "stroke-linecap": "round",
                    "stroke-width": +(s.width / c * i).toFixed(3),
                    opacity: +(s.opacity / c).toFixed(3)
                }));
            }
            return out.insertBefore(this).translate(s.offsetx, s.offsety);
        };
        var curveslengths = {},
        getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
            if (length == null) {
                return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
            } else {
                return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
            }
        },
        getLengthFactory = function (istotal, subpath) {
            return function (path, length, onlystart) {
                path = path2curve(path);
                var x, y, p, l, sp = "", subpaths = {}, point,
                    len = 0;
                for (var i = 0, ii = path.length; i < ii; i++) {
                    p = path[i];
                    if (p[0] == "M") {
                        x = +p[1];
                        y = +p[2];
                    } else {
                        l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
                        if (len + l > length) {
                            if (subpath && !subpaths.start) {
                                point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
                                sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
                                if (onlystart) {return sp;}
                                subpaths.start = sp;
                                sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
                                len += l;
                                x = +p[5];
                                y = +p[6];
                                continue;
                            }
                            if (!istotal && !subpath) {
                                point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
                                return {x: point.x, y: point.y, alpha: point.alpha};
                            }
                        }
                        len += l;
                        x = +p[5];
                        y = +p[6];
                    }
                    sp += p.shift() + p;
                }
                subpaths.end = sp;
                point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
                point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
                return point;
            };
        };
        var getTotalLength = getLengthFactory(1),
            getPointAtLength = getLengthFactory(),
            getSubpathsAtLength = getLengthFactory(0, 1);
        /*\
         * Raphael.getTotalLength
         [ method ]
         **
         * Returns length of the given path in pixels.
         **
         > Parameters
         **
         - path (string) SVG path string.
         **
         = (number) length.
        \*/
        R.getTotalLength = getTotalLength;
        /*\
         * Raphael.getPointAtLength
         [ method ]
         **
         * Return coordinates of the point located at the given length on the given path.
         **
         > Parameters
         **
         - path (string) SVG path string
         - length (number)
         **
         = (object) representation of the point:
         o {
         o     x: (number) x coordinate
         o     y: (number) y coordinate
         o     alpha: (number) angle of derivative
         o }
        \*/
        R.getPointAtLength = getPointAtLength;
        /*\
         * Raphael.getSubpath
         [ method ]
         **
         * Return subpath of a given path from given length to given length.
         **
         > Parameters
         **
         - path (string) SVG path string
         - from (number) position of the start of the segment
         - to (number) position of the end of the segment
         **
         = (string) pathstring for the segment
        \*/
        R.getSubpath = function (path, from, to) {
            if (this.getTotalLength(path) - to < 1e-6) {
                return getSubpathsAtLength(path, from).end;
            }
            var a = getSubpathsAtLength(path, to, 1);
            return from ? getSubpathsAtLength(a, from).end : a;
        };
        /*\
         * Element.getTotalLength
         [ method ]
         **
         * Returns length of the path in pixels. Only works for element of “path” type.
         = (number) length.
        \*/
        elproto.getTotalLength = function () {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            if (this.node.getTotalLength) {
                return this.node.getTotalLength();
            }
    
            return getTotalLength(path);
        };
        /*\
         * Element.getPointAtLength
         [ method ]
         **
         * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
         **
         > Parameters
         **
         - length (number)
         **
         = (object) representation of the point:
         o {
         o     x: (number) x coordinate
         o     y: (number) y coordinate
         o     alpha: (number) angle of derivative
         o }
        \*/
        elproto.getPointAtLength = function (length) {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            return getPointAtLength(path, length);
        };
        /*\
         * Element.getPath
         [ method ]
         **
         * Returns path of the element. Only works for elements of “path” type and simple elements like circle.
         = (object) path
         **
        \*/
        elproto.getPath = function () {
            var path,
                getPath = R._getPath[this.type];
            
            if (this.type == "text" || this.type == "set") {
                return;
            }
    
            if (getPath) {
                path = getPath(this);
            }
    
            return path;
        };
        /*\
         * Element.getSubpath
         [ method ]
         **
         * Return subpath of a given element from given length to given length. Only works for element of “path” type.
         **
         > Parameters
         **
         - from (number) position of the start of the segment
         - to (number) position of the end of the segment
         **
         = (string) pathstring for the segment
        \*/
        elproto.getSubpath = function (from, to) {
            var path = this.getPath();
            if (!path) {
                return;
            }
    
            return R.getSubpath(path, from, to);
        };
        /*\
         * Raphael.easing_formulas
         [ property ]
         **
         * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
         # <ul>
         #     <li>“linear”</li>
         #     <li>“&lt;” or “easeIn” or “ease-in”</li>
         #     <li>“>” or “easeOut” or “ease-out”</li>
         #     <li>“&lt;>” or “easeInOut” or “ease-in-out”</li>
         #     <li>“backIn” or “back-in”</li>
         #     <li>“backOut” or “back-out”</li>
         #     <li>“elastic”</li>
         #     <li>“bounce”</li>
         # </ul>
         # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p>
        \*/
        var ef = R.easing_formulas = {
            linear: function (n) {
                return n;
            },
            "<": function (n) {
                return pow(n, 1.7);
            },
            ">": function (n) {
                return pow(n, .48);
            },
            "<>": function (n) {
                var q = .48 - n / 1.04,
                    Q = math.sqrt(.1734 + q * q),
                    x = Q - q,
                    X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
                    y = -Q - q,
                    Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
                    t = X + Y + .5;
                return (1 - t) * 3 * t * t + t * t * t;
            },
            backIn: function (n) {
                var s = 1.70158;
                return n * n * ((s + 1) * n - s);
            },
            backOut: function (n) {
                n = n - 1;
                var s = 1.70158;
                return n * n * ((s + 1) * n + s) + 1;
            },
            elastic: function (n) {
                if (n == !!n) {
                    return n;
                }
                return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
            },
            bounce: function (n) {
                var s = 7.5625,
                    p = 2.75,
                    l;
                if (n < (1 / p)) {
                    l = s * n * n;
                } else {
                    if (n < (2 / p)) {
                        n -= (1.5 / p);
                        l = s * n * n + .75;
                    } else {
                        if (n < (2.5 / p)) {
                            n -= (2.25 / p);
                            l = s * n * n + .9375;
                        } else {
                            n -= (2.625 / p);
                            l = s * n * n + .984375;
                        }
                    }
                }
                return l;
            }
        };
        ef.easeIn = ef["ease-in"] = ef["<"];
        ef.easeOut = ef["ease-out"] = ef[">"];
        ef.easeInOut = ef["ease-in-out"] = ef["<>"];
        ef["back-in"] = ef.backIn;
        ef["back-out"] = ef.backOut;
    
        var animationElements = [],
            requestAnimFrame = window.requestAnimationFrame       ||
                               window.webkitRequestAnimationFrame ||
                               window.mozRequestAnimationFrame    ||
                               window.oRequestAnimationFrame      ||
                               window.msRequestAnimationFrame     ||
                               function (callback) {
                                   setTimeout(callback, 16);
                               },
            animation = function () {
                var Now = +new Date,
                    l = 0;
                for (; l < animationElements.length; l++) {
                    var e = animationElements[l];
                    if (e.el.removed || e.paused) {
                        continue;
                    }
                    var time = Now - e.start,
                        ms = e.ms,
                        easing = e.easing,
                        from = e.from,
                        diff = e.diff,
                        to = e.to,
                        t = e.t,
                        that = e.el,
                        set = {},
                        now,
                        init = {},
                        key;
                    if (e.initstatus) {
                        time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
                        e.status = e.initstatus;
                        delete e.initstatus;
                        e.stop && animationElements.splice(l--, 1);
                    } else {
                        e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
                    }
                    if (time < 0) {
                        continue;
                    }
                    if (time < ms) {
                        var pos = easing(time / ms);
                        for (var attr in from) if (from[has](attr)) {
                            switch (availableAnimAttrs[attr]) {
                                case nu:
                                    now = +from[attr] + pos * ms * diff[attr];
                                    break;
                                case "colour":
                                    now = "rgb(" + [
                                        upto255(round(from[attr].r + pos * ms * diff[attr].r)),
                                        upto255(round(from[attr].g + pos * ms * diff[attr].g)),
                                        upto255(round(from[attr].b + pos * ms * diff[attr].b))
                                    ].join(",") + ")";
                                    break;
                                case "path":
                                    now = [];
                                    for (var i = 0, ii = from[attr].length; i < ii; i++) {
                                        now[i] = [from[attr][i][0]];
                                        for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
                                            now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
                                        }
                                        now[i] = now[i].join(S);
                                    }
                                    now = now.join(S);
                                    break;
                                case "transform":
                                    if (diff[attr].real) {
                                        now = [];
                                        for (i = 0, ii = from[attr].length; i < ii; i++) {
                                            now[i] = [from[attr][i][0]];
                                            for (j = 1, jj = from[attr][i].length; j < jj; j++) {
                                                now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
                                            }
                                        }
                                    } else {
                                        var get = function (i) {
                                            return +from[attr][i] + pos * ms * diff[attr][i];
                                        };
                                        // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
                                        now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
                                    }
                                    break;
                                case "csv":
                                    if (attr == "clip-rect") {
                                        now = [];
                                        i = 4;
                                        while (i--) {
                                            now[i] = +from[attr][i] + pos * ms * diff[attr][i];
                                        }
                                    }
                                    break;
                                default:
                                    var from2 = [][concat](from[attr]);
                                    now = [];
                                    i = that.paper.customAttributes[attr].length;
                                    while (i--) {
                                        now[i] = +from2[i] + pos * ms * diff[attr][i];
                                    }
                                    break;
                            }
                            set[attr] = now;
                        }
                        that.attr(set);
                        (function (id, that, anim) {
                            setTimeout(function () {
                                eve("raphael.anim.frame." + id, that, anim);
                            });
                        })(that.id, that, e.anim);
                    } else {
                        (function(f, el, a) {
                            setTimeout(function() {
                                eve("raphael.anim.frame." + el.id, el, a);
                                eve("raphael.anim.finish." + el.id, el, a);
                                R.is(f, "function") && f.call(el);
                            });
                        })(e.callback, that, e.anim);
                        that.attr(to);
                        animationElements.splice(l--, 1);
                        if (e.repeat > 1 && !e.next) {
                            for (key in to) if (to[has](key)) {
                                init[key] = e.totalOrigin[key];
                            }
                            e.el.attr(init);
                            runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
                        }
                        if (e.next && !e.stop) {
                            runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
                        }
                    }
                }
                R.svg && that && that.paper && that.paper.safari();
                animationElements.length && requestAnimFrame(animation);
            },
            upto255 = function (color) {
                return color > 255 ? 255 : color < 0 ? 0 : color;
            };
        /*\
         * Element.animateWith
         [ method ]
         **
         * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element.
         **
         > Parameters
         **
         - el (object) element to sync with
         - anim (object) animation to sync with
         - params (object) #optional final attributes for the element, see also @Element.attr
         - ms (number) #optional number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         * or
         - element (object) element to sync with
         - anim (object) animation to sync with
         - animation (object) #optional animation object, see @Raphael.animation
         **
         = (object) original element
        \*/
        elproto.animateWith = function (el, anim, params, ms, easing, callback) {
            var element = this;
            if (element.removed) {
                callback && callback.call(element);
                return element;
            }
            var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
                x, y;
            runAnimation(a, element, a.percents[0], null, element.attr());
            for (var i = 0, ii = animationElements.length; i < ii; i++) {
                if (animationElements[i].anim == anim && animationElements[i].el == el) {
                    animationElements[ii - 1].start = animationElements[i].start;
                    break;
                }
            }
            return element;
            // 
            // 
            // var a = params ? R.animation(params, ms, easing, callback) : anim,
            //     status = element.status(anim);
            // return this.animate(a).status(a, status * anim.ms / a.ms);
        };
        function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
            var cx = 3 * p1x,
                bx = 3 * (p2x - p1x) - cx,
                ax = 1 - cx - bx,
                cy = 3 * p1y,
                by = 3 * (p2y - p1y) - cy,
                ay = 1 - cy - by;
            function sampleCurveX(t) {
                return ((ax * t + bx) * t + cx) * t;
            }
            function solve(x, epsilon) {
                var t = solveCurveX(x, epsilon);
                return ((ay * t + by) * t + cy) * t;
            }
            function solveCurveX(x, epsilon) {
                var t0, t1, t2, x2, d2, i;
                for(t2 = x, i = 0; i < 8; i++) {
                    x2 = sampleCurveX(t2) - x;
                    if (abs(x2) < epsilon) {
                        return t2;
                    }
                    d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
                    if (abs(d2) < 1e-6) {
                        break;
                    }
                    t2 = t2 - x2 / d2;
                }
                t0 = 0;
                t1 = 1;
                t2 = x;
                if (t2 < t0) {
                    return t0;
                }
                if (t2 > t1) {
                    return t1;
                }
                while (t0 < t1) {
                    x2 = sampleCurveX(t2);
                    if (abs(x2 - x) < epsilon) {
                        return t2;
                    }
                    if (x > x2) {
                        t0 = t2;
                    } else {
                        t1 = t2;
                    }
                    t2 = (t1 - t0) / 2 + t0;
                }
                return t2;
            }
            return solve(t, 1 / (200 * duration));
        }
        elproto.onAnimation = function (f) {
            f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
            return this;
        };
        function Animation(anim, ms) {
            var percents = [],
                newAnim = {};
            this.ms = ms;
            this.times = 1;
            if (anim) {
                for (var attr in anim) if (anim[has](attr)) {
                    newAnim[toFloat(attr)] = anim[attr];
                    percents.push(toFloat(attr));
                }
                percents.sort(sortByNumber);
            }
            this.anim = newAnim;
            this.top = percents[percents.length - 1];
            this.percents = percents;
        }
        /*\
         * Animation.delay
         [ method ]
         **
         * Creates a copy of existing animation object with given delay.
         **
         > Parameters
         **
         - delay (number) number of ms to pass between animation start and actual animation
         **
         = (object) new altered Animation object
         | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3);
         | circle1.animate(anim); // run the given animation immediately
         | circle2.animate(anim.delay(500)); // run the given animation after 500 ms
        \*/
        Animation.prototype.delay = function (delay) {
            var a = new Animation(this.anim, this.ms);
            a.times = this.times;
            a.del = +delay || 0;
            return a;
        };
        /*\
         * Animation.repeat
         [ method ]
         **
         * Creates a copy of existing animation object with given repetition.
         **
         > Parameters
         **
         - repeat (number) number iterations of animation. For infinite animation pass `Infinity`
         **
         = (object) new altered Animation object
        \*/
        Animation.prototype.repeat = function (times) {
            var a = new Animation(this.anim, this.ms);
            a.del = this.del;
            a.times = math.floor(mmax(times, 0)) || 1;
            return a;
        };
        function runAnimation(anim, element, percent, status, totalOrigin, times) {
            percent = toFloat(percent);
            var params,
                isInAnim,
                isInAnimSet,
                percents = [],
                next,
                prev,
                timestamp,
                ms = anim.ms,
                from = {},
                to = {},
                diff = {};
            if (status) {
                for (i = 0, ii = animationElements.length; i < ii; i++) {
                    var e = animationElements[i];
                    if (e.el.id == element.id && e.anim == anim) {
                        if (e.percent != percent) {
                            animationElements.splice(i, 1);
                            isInAnimSet = 1;
                        } else {
                            isInAnim = e;
                        }
                        element.attr(e.totalOrigin);
                        break;
                    }
                }
            } else {
                status = +to; // NaN
            }
            for (var i = 0, ii = anim.percents.length; i < ii; i++) {
                if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
                    percent = anim.percents[i];
                    prev = anim.percents[i - 1] || 0;
                    ms = ms / anim.top * (percent - prev);
                    next = anim.percents[i + 1];
                    params = anim.anim[percent];
                    break;
                } else if (status) {
                    element.attr(anim.anim[anim.percents[i]]);
                }
            }
            if (!params) {
                return;
            }
            if (!isInAnim) {
                for (var attr in params) if (params[has](attr)) {
                    if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
                        from[attr] = element.attr(attr);
                        (from[attr] == null) && (from[attr] = availableAttrs[attr]);
                        to[attr] = params[attr];
                        switch (availableAnimAttrs[attr]) {
                            case nu:
                                diff[attr] = (to[attr] - from[attr]) / ms;
                                break;
                            case "colour":
                                from[attr] = R.getRGB(from[attr]);
                                var toColour = R.getRGB(to[attr]);
                                diff[attr] = {
                                    r: (toColour.r - from[attr].r) / ms,
                                    g: (toColour.g - from[attr].g) / ms,
                                    b: (toColour.b - from[attr].b) / ms
                                };
                                break;
                            case "path":
                                var pathes = path2curve(from[attr], to[attr]),
                                    toPath = pathes[1];
                                from[attr] = pathes[0];
                                diff[attr] = [];
                                for (i = 0, ii = from[attr].length; i < ii; i++) {
                                    diff[attr][i] = [0];
                                    for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
                                        diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
                                    }
                                }
                                break;
                            case "transform":
                                var _ = element._,
                                    eq = equaliseTransform(_[attr], to[attr]);
                                if (eq) {
                                    from[attr] = eq.from;
                                    to[attr] = eq.to;
                                    diff[attr] = [];
                                    diff[attr].real = true;
                                    for (i = 0, ii = from[attr].length; i < ii; i++) {
                                        diff[attr][i] = [from[attr][i][0]];
                                        for (j = 1, jj = from[attr][i].length; j < jj; j++) {
                                            diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
                                        }
                                    }
                                } else {
                                    var m = (element.matrix || new Matrix),
                                        to2 = {
                                            _: {transform: _.transform},
                                            getBBox: function () {
                                                return element.getBBox(1);
                                            }
                                        };
                                    from[attr] = [
                                        m.a,
                                        m.b,
                                        m.c,
                                        m.d,
                                        m.e,
                                        m.f
                                    ];
                                    extractTransform(to2, to[attr]);
                                    to[attr] = to2._.transform;
                                    diff[attr] = [
                                        (to2.matrix.a - m.a) / ms,
                                        (to2.matrix.b - m.b) / ms,
                                        (to2.matrix.c - m.c) / ms,
                                        (to2.matrix.d - m.d) / ms,
                                        (to2.matrix.e - m.e) / ms,
                                        (to2.matrix.f - m.f) / ms
                                    ];
                                    // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
                                    // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
                                    // extractTransform(to2, to[attr]);
                                    // diff[attr] = [
                                    //     (to2._.sx - _.sx) / ms,
                                    //     (to2._.sy - _.sy) / ms,
                                    //     (to2._.deg - _.deg) / ms,
                                    //     (to2._.dx - _.dx) / ms,
                                    //     (to2._.dy - _.dy) / ms
                                    // ];
                                }
                                break;
                            case "csv":
                                var values = Str(params[attr])[split](separator),
                                    from2 = Str(from[attr])[split](separator);
                                if (attr == "clip-rect") {
                                    from[attr] = from2;
                                    diff[attr] = [];
                                    i = from2.length;
                                    while (i--) {
                                        diff[attr][i] = (values[i] - from[attr][i]) / ms;
                                    }
                                }
                                to[attr] = values;
                                break;
                            default:
                                values = [][concat](params[attr]);
                                from2 = [][concat](from[attr]);
                                diff[attr] = [];
                                i = element.paper.customAttributes[attr].length;
                                while (i--) {
                                    diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
                                }
                                break;
                        }
                    }
                }
                var easing = params.easing,
                    easyeasy = R.easing_formulas[easing];
                if (!easyeasy) {
                    easyeasy = Str(easing).match(bezierrg);
                    if (easyeasy && easyeasy.length == 5) {
                        var curve = easyeasy;
                        easyeasy = function (t) {
                            return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
                        };
                    } else {
                        easyeasy = pipe;
                    }
                }
                timestamp = params.start || anim.start || +new Date;
                e = {
                    anim: anim,
                    percent: percent,
                    timestamp: timestamp,
                    start: timestamp + (anim.del || 0),
                    status: 0,
                    initstatus: status || 0,
                    stop: false,
                    ms: ms,
                    easing: easyeasy,
                    from: from,
                    diff: diff,
                    to: to,
                    el: element,
                    callback: params.callback,
                    prev: prev,
                    next: next,
                    repeat: times || anim.times,
                    origin: element.attr(),
                    totalOrigin: totalOrigin
                };
                animationElements.push(e);
                if (status && !isInAnim && !isInAnimSet) {
                    e.stop = true;
                    e.start = new Date - ms * status;
                    if (animationElements.length == 1) {
                        return animation();
                    }
                }
                if (isInAnimSet) {
                    e.start = new Date - e.ms * status;
                }
                animationElements.length == 1 && requestAnimFrame(animation);
            } else {
                isInAnim.initstatus = status;
                isInAnim.start = new Date - isInAnim.ms * status;
            }
            eve("raphael.anim.start." + element.id, element, anim);
        }
        /*\
         * Raphael.animation
         [ method ]
         **
         * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods.
         * See also @Animation.delay and @Animation.repeat methods.
         **
         > Parameters
         **
         - params (object) final attributes for the element, see also @Element.attr
         - ms (number) number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         **
         = (object) @Animation
        \*/
        R.animation = function (params, ms, easing, callback) {
            if (params instanceof Animation) {
                return params;
            }
            if (R.is(easing, "function") || !easing) {
                callback = callback || easing || null;
                easing = null;
            }
            params = Object(params);
            ms = +ms || 0;
            var p = {},
                json,
                attr;
            for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
                json = true;
                p[attr] = params[attr];
            }
            if (!json) {
                return new Animation(params, ms);
            } else {
                easing && (p.easing = easing);
                callback && (p.callback = callback);
                return new Animation({100: p}, ms);
            }
        };
        /*\
         * Element.animate
         [ method ]
         **
         * Creates and starts animation for given element.
         **
         > Parameters
         **
         - params (object) final attributes for the element, see also @Element.attr
         - ms (number) number of milliseconds for animation to run
         - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
         - callback (function) #optional callback function. Will be called at the end of animation.
         * or
         - animation (object) animation object, see @Raphael.animation
         **
         = (object) original element
        \*/
        elproto.animate = function (params, ms, easing, callback) {
            var element = this;
            if (element.removed) {
                callback && callback.call(element);
                return element;
            }
            var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
            runAnimation(anim, element, anim.percents[0], null, element.attr());
            return element;
        };
        /*\
         * Element.setTime
         [ method ]
         **
         * Sets the status of animation of the element in milliseconds. Similar to @Element.status method.
         **
         > Parameters
         **
         - anim (object) animation object
         - value (number) number of milliseconds from the beginning of the animation
         **
         = (object) original element if `value` is specified
         * Note, that during animation following events are triggered:
         *
         * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`.
        \*/
        elproto.setTime = function (anim, value) {
            if (anim && value != null) {
                this.status(anim, mmin(value, anim.ms) / anim.ms);
            }
            return this;
        };
        /*\
         * Element.status
         [ method ]
         **
         * Gets or sets the status of animation of the element.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position.
         **
         = (number) status
         * or
         = (array) status if `anim` is not specified. Array of objects in format:
         o {
         o     anim: (object) animation object
         o     status: (number) status
         o }
         * or
         = (object) original element if `value` is specified
        \*/
        elproto.status = function (anim, value) {
            var out = [],
                i = 0,
                len,
                e;
            if (value != null) {
                runAnimation(anim, this, -1, mmin(value, 1));
                return this;
            } else {
                len = animationElements.length;
                for (; i < len; i++) {
                    e = animationElements[i];
                    if (e.el.id == this.id && (!anim || e.anim == anim)) {
                        if (anim) {
                            return e.status;
                        }
                        out.push({
                            anim: e.anim,
                            status: e.status
                        });
                    }
                }
                if (anim) {
                    return 0;
                }
                return out;
            }
        };
        /*\
         * Element.pause
         [ method ]
         **
         * Stops animation of the element with ability to resume it later on.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.pause = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
                    animationElements[i].paused = true;
                }
            }
            return this;
        };
        /*\
         * Element.resume
         [ method ]
         **
         * Resumes animation if it was paused with @Element.pause method.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.resume = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                var e = animationElements[i];
                if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
                    delete e.paused;
                    this.status(e.anim, e.status);
                }
            }
            return this;
        };
        /*\
         * Element.stop
         [ method ]
         **
         * Stops animation of the element.
         **
         > Parameters
         **
         - anim (object) #optional animation object
         **
         = (object) original element
        \*/
        elproto.stop = function (anim) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
                if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
                    animationElements.splice(i--, 1);
                }
            }
            return this;
        };
        function stopAnimation(paper) {
            for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
                animationElements.splice(i--, 1);
            }
        }
        eve.on("raphael.remove", stopAnimation);
        eve.on("raphael.clear", stopAnimation);
        elproto.toString = function () {
            return "Rapha\xebl\u2019s object";
        };
    
        // Set
        var Set = function (items) {
            this.items = [];
            this.length = 0;
            this.type = "set";
            if (items) {
                for (var i = 0, ii = items.length; i < ii; i++) {
                    if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
                        this[this.items.length] = this.items[this.items.length] = items[i];
                        this.length++;
                    }
                }
            }
        },
        setproto = Set.prototype;
        /*\
         * Set.push
         [ method ]
         **
         * Adds each argument to the current set.
         = (object) original element
        \*/
        setproto.push = function () {
            var item,
                len;
            for (var i = 0, ii = arguments.length; i < ii; i++) {
                item = arguments[i];
                if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
                    len = this.items.length;
                    this[len] = this.items[len] = item;
                    this.length++;
                }
            }
            return this;
        };
        /*\
         * Set.pop
         [ method ]
         **
         * Removes last element and returns it.
         = (object) element
        \*/
        setproto.pop = function () {
            this.length && delete this[this.length--];
            return this.items.pop();
        };
        /*\
         * Set.forEach
         [ method ]
         **
         * Executes given function for each element in the set.
         *
         * If function returns `false` it will stop loop running.
         **
         > Parameters
         **
         - callback (function) function to run
         - thisArg (object) context object for the callback
         = (object) Set object
        \*/
        setproto.forEach = function (callback, thisArg) {
            for (var i = 0, ii = this.items.length; i < ii; i++) {
                if (callback.call(thisArg, this.items[i], i) === false) {
                    return this;
                }
            }
            return this;
        };
        for (var method in elproto) if (elproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname][apply](el, arg);
                    });
                };
            })(method);
        }
        setproto.attr = function (name, value) {
            if (name && R.is(name, array) && R.is(name[0], "object")) {
                for (var j = 0, jj = name.length; j < jj; j++) {
                    this.items[j].attr(name[j]);
                }
            } else {
                for (var i = 0, ii = this.items.length; i < ii; i++) {
                    this.items[i].attr(name, value);
                }
            }
            return this;
        };
        /*\
         * Set.clear
         [ method ]
         **
         * Removeds all elements from the set
        \*/
        setproto.clear = function () {
            while (this.length) {
                this.pop();
            }
        };
        /*\
         * Set.splice
         [ method ]
         **
         * Removes given element from the set
         **
         > Parameters
         **
         - index (number) position of the deletion
         - count (number) number of element to remove
         - insertion… (object) #optional elements to insert
         = (object) set elements that were deleted
        \*/
        setproto.splice = function (index, count, insertion) {
            index = index < 0 ? mmax(this.length + index, 0) : index;
            count = mmax(0, mmin(this.length - index, count));
            var tail = [],
                todel = [],
                args = [],
                i;
            for (i = 2; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            for (i = 0; i < count; i++) {
                todel.push(this[index + i]);
            }
            for (; i < this.length - index; i++) {
                tail.push(this[index + i]);
            }
            var arglen = args.length;
            for (i = 0; i < arglen + tail.length; i++) {
                this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
            }
            i = this.items.length = this.length -= count - arglen;
            while (this[i]) {
                delete this[i++];
            }
            return new Set(todel);
        };
        /*\
         * Set.exclude
         [ method ]
         **
         * Removes given element from the set
         **
         > Parameters
         **
         - element (object) element to remove
         = (boolean) `true` if object was found & removed from the set
        \*/
        setproto.exclude = function (el) {
            for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
                this.splice(i, 1);
                return true;
            }
        };
        setproto.animate = function (params, ms, easing, callback) {
            (R.is(easing, "function") || !easing) && (callback = easing || null);
            var len = this.items.length,
                i = len,
                item,
                set = this,
                collector;
            if (!len) {
                return this;
            }
            callback && (collector = function () {
                !--len && callback.call(set);
            });
            easing = R.is(easing, string) ? easing : collector;
            var anim = R.animation(params, ms, easing, collector);
            item = this.items[--i].animate(anim);
            while (i--) {
                this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
                (this.items[i] && !this.items[i].removed) || len--;
            }
            return this;
        };
        setproto.insertAfter = function (el) {
            var i = this.items.length;
            while (i--) {
                this.items[i].insertAfter(el);
            }
            return this;
        };
        setproto.getBBox = function () {
            var x = [],
                y = [],
                x2 = [],
                y2 = [];
            for (var i = this.items.length; i--;) if (!this.items[i].removed) {
                var box = this.items[i].getBBox();
                x.push(box.x);
                y.push(box.y);
                x2.push(box.x + box.width);
                y2.push(box.y + box.height);
            }
            x = mmin[apply](0, x);
            y = mmin[apply](0, y);
            x2 = mmax[apply](0, x2);
            y2 = mmax[apply](0, y2);
            return {
                x: x,
                y: y,
                x2: x2,
                y2: y2,
                width: x2 - x,
                height: y2 - y
            };
        };
        setproto.clone = function (s) {
            s = this.paper.set();
            for (var i = 0, ii = this.items.length; i < ii; i++) {
                s.push(this.items[i].clone());
            }
            return s;
        };
        setproto.toString = function () {
            return "Rapha\xebl\u2018s set";
        };
    
        setproto.glow = function(glowConfig) {
            var ret = this.paper.set();
            this.forEach(function(shape, index){
                var g = shape.glow(glowConfig);
                if(g != null){
                    g.forEach(function(shape2, index2){
                        ret.push(shape2);
                    });
                }
            });
            return ret;
        };
    
    
        /*\
         * Set.isPointInside
         [ method ]
         **
         * Determine if given point is inside this set’s elements
         **
         > Parameters
         **
         - x (number) x coordinate of the point
         - y (number) y coordinate of the point
         = (boolean) `true` if point is inside any of the set's elements
         \*/
        setproto.isPointInside = function (x, y) {
            var isPointInside = false;
            this.forEach(function (el) {
                if (el.isPointInside(x, y)) {
                    console.log('runned');
                    isPointInside = true;
                    return false; // stop loop
                }
            });
            return isPointInside;
        };
    
        /*\
         * Raphael.registerFont
         [ method ]
         **
         * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file.
         * Returns original parameter, so it could be used with chaining.
         # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a>
         **
         > Parameters
         **
         - font (object) the font to register
         = (object) the font you passed in
         > Usage
         | Cufon.registerFont(Raphael.registerFont({…}));
        \*/
        R.registerFont = function (font) {
            if (!font.face) {
                return font;
            }
            this.fonts = this.fonts || {};
            var fontcopy = {
                    w: font.w,
                    face: {},
                    glyphs: {}
                },
                family = font.face["font-family"];
            for (var prop in font.face) if (font.face[has](prop)) {
                fontcopy.face[prop] = font.face[prop];
            }
            if (this.fonts[family]) {
                this.fonts[family].push(fontcopy);
            } else {
                this.fonts[family] = [fontcopy];
            }
            if (!font.svg) {
                fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
                for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
                    var path = font.glyphs[glyph];
                    fontcopy.glyphs[glyph] = {
                        w: path.w,
                        k: {},
                        d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
                                return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
                            }) + "z"
                    };
                    if (path.k) {
                        for (var k in path.k) if (path[has](k)) {
                            fontcopy.glyphs[glyph].k[k] = path.k[k];
                        }
                    }
                }
            }
            return font;
        };
        /*\
         * Paper.getFont
         [ method ]
         **
         * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”.
         **
         > Parameters
         **
         - family (string) font family name or any word from it
         - weight (string) #optional font weight
         - style (string) #optional font style
         - stretch (string) #optional font stretch
         = (object) the font object
         > Usage
         | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30);
        \*/
        paperproto.getFont = function (family, weight, style, stretch) {
            stretch = stretch || "normal";
            style = style || "normal";
            weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
            if (!R.fonts) {
                return;
            }
            var font = R.fonts[family];
            if (!font) {
                var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
                for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
                    if (name.test(fontName)) {
                        font = R.fonts[fontName];
                        break;
                    }
                }
            }
            var thefont;
            if (font) {
                for (var i = 0, ii = font.length; i < ii; i++) {
                    thefont = font[i];
                    if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
                        break;
                    }
                }
            }
            return thefont;
        };
        /*\
         * Paper.print
         [ method ]
         **
         * Creates path that represent given text written using given font at given position with given size.
         * Result of the method is path element that contains whole text as a separate path.
         **
         > Parameters
         **
         - x (number) x position of the text
         - y (number) y position of the text
         - string (string) text to print
         - font (object) font object, see @Paper.getFont
         - size (number) #optional size of the font, default is `16`
         - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"`
         - letter_spacing (number) #optional number in range `-1..1`, default is `0`
         - line_spacing (number) #optional number in range `1..3`, default is `1`
         = (object) resulting path element, which consist of all letters
         > Usage
         | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"});
        \*/
        paperproto.print = function (x, y, string, font, size, origin, letter_spacing, line_spacing) {
            origin = origin || "middle"; // baseline|middle
            letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
            line_spacing = mmax(mmin(line_spacing || 1, 3), 1);
            var letters = Str(string)[split](E),
                shift = 0,
                notfirst = 0,
                path = E,
                scale;
            R.is(font, "string") && (font = this.getFont(font));
            if (font) {
                scale = (size || 16) / font.face["units-per-em"];
                var bb = font.face.bbox[split](separator),
                    top = +bb[0],
                    lineHeight = bb[3] - bb[1],
                    shifty = 0,
                    height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
                for (var i = 0, ii = letters.length; i < ii; i++) {
                    if (letters[i] == "\n") {
                        shift = 0;
                        curr = 0;
                        notfirst = 0;
                        shifty += lineHeight * line_spacing;
                    } else {
                        var prev = notfirst && font.glyphs[letters[i - 1]] || {},
                            curr = font.glyphs[letters[i]];
                        shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
                        notfirst = 1;
                    }
                    if (curr && curr.d) {
                        path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
                    }
                }
            }
            return this.path(path).attr({
                fill: "#000",
                stroke: "none"
            });
        };
    
        /*\
         * Paper.add
         [ method ]
         **
         * Imports elements in JSON array in format `{type: type, <attributes>}`
         **
         > Parameters
         **
         - json (array)
         = (object) resulting set of imported elements
         > Usage
         | paper.add([
         |     {
         |         type: "circle",
         |         cx: 10,
         |         cy: 10,
         |         r: 5
         |     },
         |     {
         |         type: "rect",
         |         x: 10,
         |         y: 10,
         |         width: 10,
         |         height: 10,
         |         fill: "#fc0"
         |     }
         | ]);
        \*/
        paperproto.add = function (json) {
            if (R.is(json, "array")) {
                var res = this.set(),
                    i = 0,
                    ii = json.length,
                    j;
                for (; i < ii; i++) {
                    j = json[i] || {};
                    elements[has](j.type) && res.push(this[j.type]().attr(j));
                }
            }
            return res;
        };
    
        /*\
         * Raphael.format
         [ method ]
         **
         * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument.
         **
         > Parameters
         **
         - token (string) string to format
         - … (string) rest of arguments will be treated as parameters for replacement
         = (string) formated string
         > Usage
         | var x = 10,
         |     y = 20,
         |     width = 40,
         |     height = 50;
         | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
         | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width));
        \*/
        R.format = function (token, params) {
            var args = R.is(params, array) ? [0][concat](params) : arguments;
            token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
                return args[++i] == null ? E : args[i];
            }));
            return token || E;
        };
        /*\
         * Raphael.fullfill
         [ method ]
         **
         * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument.
         **
         > Parameters
         **
         - token (string) string to format
         - json (object) object which properties will be used as a replacement
         = (string) formated string
         > Usage
         | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
         | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
         |     x: 10,
         |     y: 20,
         |     dim: {
         |         width: 40,
         |         height: 50,
         |         "negative width": -40
         |     }
         | }));
        \*/
        R.fullfill = (function () {
            var tokenRegex = /\{([^\}]+)\}/g,
                objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
                replacer = function (all, key, obj) {
                    var res = obj;
                    key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
                        name = name || quotedName;
                        if (res) {
                            if (name in res) {
                                res = res[name];
                            }
                            typeof res == "function" && isFunc && (res = res());
                        }
                    });
                    res = (res == null || res == obj ? all : res) + "";
                    return res;
                };
            return function (str, obj) {
                return String(str).replace(tokenRegex, function (all, key) {
                    return replacer(all, key, obj);
                });
            };
        })();
        /*\
         * Raphael.ninja
         [ method ]
         **
         * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method.
         * Beware, that in this case plugins could stop working, because they are depending on global variable existance.
         **
         = (object) Raphael object
         > Usage
         | (function (local_raphael) {
         |     var paper = local_raphael(10, 10, 320, 200);
         |     …
         | })(Raphael.ninja());
        \*/
        R.ninja = function () {
            oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
            return R;
        };
        /*\
         * Raphael.st
         [ property (object) ]
         **
         * You can add your own method to elements and sets. It is wise to add a set method for each element method
         * you added, so you will be able to call the same method on sets too.
         **
         * See also @Raphael.el.
         > Usage
         | Raphael.el.red = function () {
         |     this.attr({fill: "#f00"});
         | };
         | Raphael.st.red = function () {
         |     this.forEach(function (el) {
         |         el.red();
         |     });
         | };
         | // then use it
         | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red();
        \*/
        R.st = setproto;
        // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
        (function (doc, loaded, f) {
            if (doc.readyState == null && doc.addEventListener){
                doc.addEventListener(loaded, f = function () {
                    doc.removeEventListener(loaded, f, false);
                    doc.readyState = "complete";
                }, false);
                doc.readyState = "loading";
            }
            function isLoaded() {
                (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
            }
            isLoaded();
        })(document, "DOMContentLoaded");
    
        eve.on("raphael.DOMload", function () {
            loaded = true;
        });
    
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël - JavaScript Vector Library                                 │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ SVG Module                                                          │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function(){
        if (!R.svg) {
            return;
        }
        var has = "hasOwnProperty",
            Str = String,
            toFloat = parseFloat,
            toInt = parseInt,
            math = Math,
            mmax = math.max,
            abs = math.abs,
            pow = math.pow,
            separator = /[, ]+/,
            eve = R.eve,
            E = "",
            S = " ";
        var xlink = "http://www.w3.org/1999/xlink",
            markers = {
                block: "M5,0 0,2.5 5,5z",
                classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
                diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
                open: "M6,1 1,3.5 6,6",
                oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
            },
            markerCounter = {};
        R.toString = function () {
            return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
        };
        var $ = function (el, attr) {
            if (attr) {
                if (typeof el == "string") {
                    el = $(el);
                }
                for (var key in attr) if (attr[has](key)) {
                    if (key.substring(0, 6) == "xlink:") {
                        el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
                    } else {
                        el.setAttribute(key, Str(attr[key]));
                    }
                }
            } else {
                el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
                el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
            }
            return el;
        },
        addGradientFill = function (element, gradient) {
            var type = "linear",
                id = element.id + gradient,
                fx = .5, fy = .5,
                o = element.node,
                SVG = element.paper,
                s = o.style,
                el = R._g.doc.getElementById(id);
            if (!el) {
                gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
                    type = "radial";
                    if (_fx && _fy) {
                        fx = toFloat(_fx);
                        fy = toFloat(_fy);
                        var dir = ((fy > .5) * 2 - 1);
                        pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
                            (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
                            fy != .5 &&
                            (fy = fy.toFixed(5) - 1e-5 * dir);
                    }
                    return E;
                });
                gradient = gradient.split(/\s*\-\s*/);
                if (type == "linear") {
                    var angle = gradient.shift();
                    angle = -toFloat(angle);
                    if (isNaN(angle)) {
                        return null;
                    }
                    var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
                        max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
                    vector[2] *= max;
                    vector[3] *= max;
                    if (vector[2] < 0) {
                        vector[0] = -vector[2];
                        vector[2] = 0;
                    }
                    if (vector[3] < 0) {
                        vector[1] = -vector[3];
                        vector[3] = 0;
                    }
                }
                var dots = R._parseDots(gradient);
                if (!dots) {
                    return null;
                }
                id = id.replace(/[\(\)\s,\xb0#]/g, "_");
                
                if (element.gradient && id != element.gradient.id) {
                    SVG.defs.removeChild(element.gradient);
                    delete element.gradient;
                }
    
                if (!element.gradient) {
                    el = $(type + "Gradient", {id: id});
                    element.gradient = el;
                    $(el, type == "radial" ? {
                        fx: fx,
                        fy: fy
                    } : {
                        x1: vector[0],
                        y1: vector[1],
                        x2: vector[2],
                        y2: vector[3],
                        gradientTransform: element.matrix.invert()
                    });
                    SVG.defs.appendChild(el);
                    for (var i = 0, ii = dots.length; i < ii; i++) {
                        el.appendChild($("stop", {
                            offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
                            "stop-color": dots[i].color || "#fff"
                        }));
                    }
                }
            }
            $(o, {
                fill: "url(#" + id + ")",
                opacity: 1,
                "fill-opacity": 1
            });
            s.fill = E;
            s.opacity = 1;
            s.fillOpacity = 1;
            return 1;
        },
        updatePosition = function (o) {
            var bbox = o.getBBox(1);
            $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
        },
        addArrow = function (o, value, isEnd) {
            if (o.type == "path") {
                var values = Str(value).toLowerCase().split("-"),
                    p = o.paper,
                    se = isEnd ? "end" : "start",
                    node = o.node,
                    attrs = o.attrs,
                    stroke = attrs["stroke-width"],
                    i = values.length,
                    type = "classic",
                    from,
                    to,
                    dx,
                    refX,
                    attr,
                    w = 3,
                    h = 3,
                    t = 5;
                while (i--) {
                    switch (values[i]) {
                        case "block":
                        case "classic":
                        case "oval":
                        case "diamond":
                        case "open":
                        case "none":
                            type = values[i];
                            break;
                        case "wide": h = 5; break;
                        case "narrow": h = 2; break;
                        case "long": w = 5; break;
                        case "short": w = 2; break;
                    }
                }
                if (type == "open") {
                    w += 2;
                    h += 2;
                    t += 2;
                    dx = 1;
                    refX = isEnd ? 4 : 1;
                    attr = {
                        fill: "none",
                        stroke: attrs.stroke
                    };
                } else {
                    refX = dx = w / 2;
                    attr = {
                        fill: attrs.stroke,
                        stroke: "none"
                    };
                }
                if (o._.arrows) {
                    if (isEnd) {
                        o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
                        o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
                    } else {
                        o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
                        o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
                    }
                } else {
                    o._.arrows = {};
                }
                if (type != "none") {
                    var pathId = "raphael-marker-" + type,
                        markerId = "raphael-marker-" + se + type + w + h;
                    if (!R._g.doc.getElementById(pathId)) {
                        p.defs.appendChild($($("path"), {
                            "stroke-linecap": "round",
                            d: markers[type],
                            id: pathId
                        }));
                        markerCounter[pathId] = 1;
                    } else {
                        markerCounter[pathId]++;
                    }
                    var marker = R._g.doc.getElementById(markerId),
                        use;
                    if (!marker) {
                        marker = $($("marker"), {
                            id: markerId,
                            markerHeight: h,
                            markerWidth: w,
                            orient: "auto",
                            refX: refX,
                            refY: h / 2
                        });
                        use = $($("use"), {
                            "xlink:href": "#" + pathId,
                            transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
                            "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
                        });
                        marker.appendChild(use);
                        p.defs.appendChild(marker);
                        markerCounter[markerId] = 1;
                    } else {
                        markerCounter[markerId]++;
                        use = marker.getElementsByTagName("use")[0];
                    }
                    $(use, attr);
                    var delta = dx * (type != "diamond" && type != "oval");
                    if (isEnd) {
                        from = o._.arrows.startdx * stroke || 0;
                        to = R.getTotalLength(attrs.path) - delta * stroke;
                    } else {
                        from = delta * stroke;
                        to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
                    }
                    attr = {};
                    attr["marker-" + se] = "url(#" + markerId + ")";
                    if (to || from) {
                        attr.d = R.getSubpath(attrs.path, from, to);
                    }
                    $(node, attr);
                    o._.arrows[se + "Path"] = pathId;
                    o._.arrows[se + "Marker"] = markerId;
                    o._.arrows[se + "dx"] = delta;
                    o._.arrows[se + "Type"] = type;
                    o._.arrows[se + "String"] = value;
                } else {
                    if (isEnd) {
                        from = o._.arrows.startdx * stroke || 0;
                        to = R.getTotalLength(attrs.path) - from;
                    } else {
                        from = 0;
                        to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
                    }
                    o._.arrows[se + "Path"] && $(node, {d: R.getSubpath(attrs.path, from, to)});
                    delete o._.arrows[se + "Path"];
                    delete o._.arrows[se + "Marker"];
                    delete o._.arrows[se + "dx"];
                    delete o._.arrows[se + "Type"];
                    delete o._.arrows[se + "String"];
                }
                for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
                    var item = R._g.doc.getElementById(attr);
                    item && item.parentNode.removeChild(item);
                }
            }
        },
        dasharray = {
            "": [0],
            "none": [0],
            "-": [3, 1],
            ".": [1, 1],
            "-.": [3, 1, 1, 1],
            "-..": [3, 1, 1, 1, 1, 1],
            ". ": [1, 3],
            "- ": [4, 3],
            "--": [8, 3],
            "- .": [4, 3, 1, 3],
            "--.": [8, 3, 1, 3],
            "--..": [8, 3, 1, 3, 1, 3]
        },
        addDashes = function (o, value, params) {
            value = dasharray[Str(value).toLowerCase()];
            if (value) {
                var width = o.attrs["stroke-width"] || "1",
                    butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
                    dashes = [],
                    i = value.length;
                while (i--) {
                    dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
                }
                $(o.node, {"stroke-dasharray": dashes.join(",")});
            }
        },
        setFillAndStroke = function (o, params) {
            var node = o.node,
                attrs = o.attrs,
                vis = node.style.visibility;
            node.style.visibility = "hidden";
            for (var att in params) {
                if (params[has](att)) {
                    if (!R._availableAttrs[has](att)) {
                        continue;
                    }
                    var value = params[att];
                    attrs[att] = value;
                    switch (att) {
                        case "blur":
                            o.blur(value);
                            break;
                        case "href":
                        case "title":
                            var hl = $("title");
                            var val = R._g.doc.createTextNode(value);
                            hl.appendChild(val);
                            node.appendChild(hl);
                            break;
                        case "target":
                            var pn = node.parentNode;
                            if (pn.tagName.toLowerCase() != "a") {
                                var hl = $("a");
                                pn.insertBefore(hl, node);
                                hl.appendChild(node);
                                pn = hl;
                            }
                            if (att == "target") {
                                pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
                            } else {
                                pn.setAttributeNS(xlink, att, value);
                            }
                            break;
                        case "cursor":
                            node.style.cursor = value;
                            break;
                        case "transform":
                            o.transform(value);
                            break;
                        case "arrow-start":
                            addArrow(o, value);
                            break;
                        case "arrow-end":
                            addArrow(o, value, 1);
                            break;
                        case "clip-rect":
                            var rect = Str(value).split(separator);
                            if (rect.length == 4) {
                                o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
                                var el = $("clipPath"),
                                    rc = $("rect");
                                el.id = R.createUUID();
                                $(rc, {
                                    x: rect[0],
                                    y: rect[1],
                                    width: rect[2],
                                    height: rect[3]
                                });
                                el.appendChild(rc);
                                o.paper.defs.appendChild(el);
                                $(node, {"clip-path": "url(#" + el.id + ")"});
                                o.clip = rc;
                            }
                            if (!value) {
                                var path = node.getAttribute("clip-path");
                                if (path) {
                                    var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
                                    clip && clip.parentNode.removeChild(clip);
                                    $(node, {"clip-path": E});
                                    delete o.clip;
                                }
                            }
                        break;
                        case "path":
                            if (o.type == "path") {
                                $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
                                o._.dirty = 1;
                                if (o._.arrows) {
                                    "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                    "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                                }
                            }
                            break;
                        case "width":
                            node.setAttribute(att, value);
                            o._.dirty = 1;
                            if (attrs.fx) {
                                att = "x";
                                value = attrs.x;
                            } else {
                                break;
                            }
                        case "x":
                            if (attrs.fx) {
                                value = -attrs.x - (attrs.width || 0);
                            }
                        case "rx":
                            if (att == "rx" && o.type == "rect") {
                                break;
                            }
                        case "cx":
                            node.setAttribute(att, value);
                            o.pattern && updatePosition(o);
                            o._.dirty = 1;
                            break;
                        case "height":
                            node.setAttribute(att, value);
                            o._.dirty = 1;
                            if (attrs.fy) {
                                att = "y";
                                value = attrs.y;
                            } else {
                                break;
                            }
                        case "y":
                            if (attrs.fy) {
                                value = -attrs.y - (attrs.height || 0);
                            }
                        case "ry":
                            if (att == "ry" && o.type == "rect") {
                                break;
                            }
                        case "cy":
                            node.setAttribute(att, value);
                            o.pattern && updatePosition(o);
                            o._.dirty = 1;
                            break;
                        case "r":
                            if (o.type == "rect") {
                                $(node, {rx: value, ry: value});
                            } else {
                                node.setAttribute(att, value);
                            }
                            o._.dirty = 1;
                            break;
                        case "src":
                            if (o.type == "image") {
                                node.setAttributeNS(xlink, "href", value);
                            }
                            break;
                        case "stroke-width":
                            if (o._.sx != 1 || o._.sy != 1) {
                                value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
                            }
                            if (o.paper._vbSize) {
                                value *= o.paper._vbSize;
                            }
                            node.setAttribute(att, value);
                            if (attrs["stroke-dasharray"]) {
                                addDashes(o, attrs["stroke-dasharray"], params);
                            }
                            if (o._.arrows) {
                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                            }
                            break;
                        case "stroke-dasharray":
                            addDashes(o, value, params);
                            break;
                        case "fill":
                            var isURL = Str(value).match(R._ISURL);
                            if (isURL) {
                                el = $("pattern");
                                var ig = $("image");
                                el.id = R.createUUID();
                                $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
                                $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
                                el.appendChild(ig);
    
                                (function (el) {
                                    R._preload(isURL[1], function () {
                                        var w = this.offsetWidth,
                                            h = this.offsetHeight;
                                        $(el, {width: w, height: h});
                                        $(ig, {width: w, height: h});
                                        o.paper.safari();
                                    });
                                })(el);
                                o.paper.defs.appendChild(el);
                                $(node, {fill: "url(#" + el.id + ")"});
                                o.pattern = el;
                                o.pattern && updatePosition(o);
                                break;
                            }
                            var clr = R.getRGB(value);
                            if (!clr.error) {
                                delete params.gradient;
                                delete attrs.gradient;
                                !R.is(attrs.opacity, "undefined") &&
                                    R.is(params.opacity, "undefined") &&
                                    $(node, {opacity: attrs.opacity});
                                !R.is(attrs["fill-opacity"], "undefined") &&
                                    R.is(params["fill-opacity"], "undefined") &&
                                    $(node, {"fill-opacity": attrs["fill-opacity"]});
                            } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
                                if ("opacity" in attrs || "fill-opacity" in attrs) {
                                    var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
                                    if (gradient) {
                                        var stops = gradient.getElementsByTagName("stop");
                                        $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
                                    }
                                }
                                attrs.gradient = value;
                                attrs.fill = "none";
                                break;
                            }
                            clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
                        case "stroke":
                            clr = R.getRGB(value);
                            node.setAttribute(att, clr.hex);
                            att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
                            if (att == "stroke" && o._.arrows) {
                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
                            }
                            break;
                        case "gradient":
                            (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
                            break;
                        case "opacity":
                            if (attrs.gradient && !attrs[has]("stroke-opacity")) {
                                $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
                            }
                            // fall
                        case "fill-opacity":
                            if (attrs.gradient) {
                                gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
                                if (gradient) {
                                    stops = gradient.getElementsByTagName("stop");
                                    $(stops[stops.length - 1], {"stop-opacity": value});
                                }
                                break;
                            }
                        default:
                            att == "font-size" && (value = toInt(value, 10) + "px");
                            var cssrule = att.replace(/(\-.)/g, function (w) {
                                return w.substring(1).toUpperCase();
                            });
                            node.style[cssrule] = value;
                            o._.dirty = 1;
                            node.setAttribute(att, value);
                            break;
                    }
                }
            }
    
            tuneText(o, params);
            node.style.visibility = vis;
        },
        leading = 1.2,
        tuneText = function (el, params) {
            if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
                return;
            }
            var a = el.attrs,
                node = el.node,
                fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
    
            if (params[has]("text")) {
                a.text = params.text;
                while (node.firstChild) {
                    node.removeChild(node.firstChild);
                }
                var texts = Str(params.text).split("\n"),
                    tspans = [],
                    tspan;
                for (var i = 0, ii = texts.length; i < ii; i++) {
                    tspan = $("tspan");
                    i && $(tspan, {dy: fontSize * leading, x: a.x});
                    tspan.appendChild(R._g.doc.createTextNode(texts[i]));
                    node.appendChild(tspan);
                    tspans[i] = tspan;
                }
            } else {
                tspans = node.getElementsByTagName("tspan");
                for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
                    $(tspans[i], {dy: fontSize * leading, x: a.x});
                } else {
                    $(tspans[0], {dy: 0});
                }
            }
            $(node, {x: a.x, y: a.y});
            el._.dirty = 1;
            var bb = el._getBBox(),
                dif = a.y - (bb.y + bb.height / 2);
            dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
        },
        Element = function (node, svg) {
            var X = 0,
                Y = 0;
            /*\
             * Element.node
             [ property (object) ]
             **
             * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
             **
             * Note: Don’t mess with it.
             > Usage
             | // draw a circle at coordinate 10,10 with radius of 10
             | var c = paper.circle(10, 10, 10);
             | c.node.onclick = function () {
             |     c.attr("fill", "red");
             | };
            \*/
            this[0] = this.node = node;
            /*\
             * Element.raphael
             [ property (object) ]
             **
             * Internal reference to @Raphael object. In case it is not available.
             > Usage
             | Raphael.el.red = function () {
             |     var hsb = this.paper.raphael.rgb2hsb(this.attr("fill"));
             |     hsb.h = 1;
             |     this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex});
             | }
            \*/
            node.raphael = true;
            /*\
             * Element.id
             [ property (number) ]
             **
             * Unique id of the element. Especially usesful when you want to listen to events of the element, 
             * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method.
            \*/
            this.id = R._oid++;
            node.raphaelid = this.id;
            this.matrix = R.matrix();
            this.realPath = null;
            /*\
             * Element.paper
             [ property (object) ]
             **
             * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions.
             > Usage
             | Raphael.el.cross = function () {
             |     this.attr({fill: "red"});
             |     this.paper.path("M10,10L50,50M50,10L10,50")
             |         .attr({stroke: "red"});
             | }
            \*/
            this.paper = svg;
            this.attrs = this.attrs || {};
            this._ = {
                transform: [],
                sx: 1,
                sy: 1,
                deg: 0,
                dx: 0,
                dy: 0,
                dirty: 1
            };
            !svg.bottom && (svg.bottom = this);
            /*\
             * Element.prev
             [ property (object) ]
             **
             * Reference to the previous element in the hierarchy.
            \*/
            this.prev = svg.top;
            svg.top && (svg.top.next = this);
            svg.top = this;
            /*\
             * Element.next
             [ property (object) ]
             **
             * Reference to the next element in the hierarchy.
            \*/
            this.next = null;
        },
        elproto = R.el;
    
        Element.prototype = elproto;
        elproto.constructor = Element;
    
        R._engine.path = function (pathString, SVG) {
            var el = $("path");
            SVG.canvas && SVG.canvas.appendChild(el);
            var p = new Element(el, SVG);
            p.type = "path";
            setFillAndStroke(p, {
                fill: "none",
                stroke: "#000",
                path: pathString
            });
            return p;
        };
        /*\
         * Element.rotate
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds rotation by given angle around given point to the list of
         * transformations of the element.
         > Parameters
         - deg (number) angle in degrees
         - cx (number) #optional x coordinate of the centre of rotation
         - cy (number) #optional y coordinate of the centre of rotation
         * If cx & cy aren’t specified centre of the shape is used as a point of rotation.
         = (object) @Element
        \*/
        elproto.rotate = function (deg, cx, cy) {
            if (this.removed) {
                return this;
            }
            deg = Str(deg).split(separator);
            if (deg.length - 1) {
                cx = toFloat(deg[1]);
                cy = toFloat(deg[2]);
            }
            deg = toFloat(deg[0]);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
                cx = bbox.x + bbox.width / 2;
                cy = bbox.y + bbox.height / 2;
            }
            this.transform(this._.transform.concat([["r", deg, cx, cy]]));
            return this;
        };
        /*\
         * Element.scale
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds scale by given amount relative to given point to the list of
         * transformations of the element.
         > Parameters
         - sx (number) horisontal scale amount
         - sy (number) vertical scale amount
         - cx (number) #optional x coordinate of the centre of scale
         - cy (number) #optional y coordinate of the centre of scale
         * If cx & cy aren’t specified centre of the shape is used instead.
         = (object) @Element
        \*/
        elproto.scale = function (sx, sy, cx, cy) {
            if (this.removed) {
                return this;
            }
            sx = Str(sx).split(separator);
            if (sx.length - 1) {
                sy = toFloat(sx[1]);
                cx = toFloat(sx[2]);
                cy = toFloat(sx[3]);
            }
            sx = toFloat(sx[0]);
            (sy == null) && (sy = sx);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
            }
            cx = cx == null ? bbox.x + bbox.width / 2 : cx;
            cy = cy == null ? bbox.y + bbox.height / 2 : cy;
            this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
            return this;
        };
        /*\
         * Element.translate
         [ method ]
         **
         * Deprecated! Use @Element.transform instead.
         * Adds translation by given amount to the list of transformations of the element.
         > Parameters
         - dx (number) horisontal shift
         - dy (number) vertical shift
         = (object) @Element
        \*/
        elproto.translate = function (dx, dy) {
            if (this.removed) {
                return this;
            }
            dx = Str(dx).split(separator);
            if (dx.length - 1) {
                dy = toFloat(dx[1]);
            }
            dx = toFloat(dx[0]) || 0;
            dy = +dy || 0;
            this.transform(this._.transform.concat([["t", dx, dy]]));
            return this;
        };
        /*\
         * Element.transform
         [ method ]
         **
         * Adds transformation to the element which is separate to other attributes,
         * i.e. translation doesn’t change `x` or `y` of the rectange. The format
         * of transformation string is similar to the path string syntax:
         | "t100,100r30,100,100s2,2,100,100r45s1.5"
         * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for
         * scale and `m` is for matrix.
         *
         * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`.
         *
         * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100;
         * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin
         * coordinates as optional parameters, the default is the centre point of the element.
         * Matrix accepts six parameters.
         > Usage
         | var el = paper.rect(10, 20, 300, 200);
         | // translate 100, 100, rotate 45°, translate -100, 0
         | el.transform("t100,100r45t-100,0");
         | // if you want you can append or prepend transformations
         | el.transform("...t50,50");
         | el.transform("s2...");
         | // or even wrap
         | el.transform("t50,50...t-50-50");
         | // to reset transformation call method with empty string
         | el.transform("");
         | // to get current value call it without parameters
         | console.log(el.transform());
         > Parameters
         - tstr (string) #optional transformation string
         * If tstr isn’t specified
         = (string) current transformation string
         * else
         = (object) @Element
        \*/
        elproto.transform = function (tstr) {
            var _ = this._;
            if (tstr == null) {
                return _.transform;
            }
            R._extractTransform(this, tstr);
    
            this.clip && $(this.clip, {transform: this.matrix.invert()});
            this.pattern && updatePosition(this);
            this.node && $(this.node, {transform: this.matrix});
        
            if (_.sx != 1 || _.sy != 1) {
                var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
                this.attr({"stroke-width": sw});
            }
    
            return this;
        };
        /*\
         * Element.hide
         [ method ]
         **
         * Makes element invisible. See @Element.show.
         = (object) @Element
        \*/
        elproto.hide = function () {
            !this.removed && this.paper.safari(this.node.style.display = "none");
            return this;
        };
        /*\
         * Element.show
         [ method ]
         **
         * Makes element visible. See @Element.hide.
         = (object) @Element
        \*/
        elproto.show = function () {
            !this.removed && this.paper.safari(this.node.style.display = "");
            return this;
        };
        /*\
         * Element.remove
         [ method ]
         **
         * Removes element from the paper.
        \*/
        elproto.remove = function () {
            if (this.removed || !this.node.parentNode) {
                return;
            }
            var paper = this.paper;
            paper.__set__ && paper.__set__.exclude(this);
            eve.unbind("raphael.*.*." + this.id);
            if (this.gradient) {
                paper.defs.removeChild(this.gradient);
            }
            R._tear(this, paper);
            if (this.node.parentNode.tagName.toLowerCase() == "a") {
                this.node.parentNode.parentNode.removeChild(this.node.parentNode);
            } else {
                this.node.parentNode.removeChild(this.node);
            }
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            this.removed = true;
        };
        elproto._getBBox = function () {
            if (this.node.style.display == "none") {
                this.show();
                var hide = true;
            }
            var bbox = {};
            try {
                bbox = this.node.getBBox();
            } catch(e) {
                // Firefox 3.0.x plays badly here
            } finally {
                bbox = bbox || {};
            }
            hide && this.hide();
            return bbox;
        };
        /*\
         * Element.attr
         [ method ]
         **
         * Sets the attributes of the element.
         > Parameters
         - attrName (string) attribute’s name
         - value (string) value
         * or
         - params (object) object of name/value pairs
         * or
         - attrName (string) attribute’s name
         * or
         - attrNames (array) in this case method returns array of current values for given attribute names
         = (object) @Element if attrsName & value or params are passed in.
         = (...) value of the attribute if only attrsName is passed in.
         = (array) array of values of the attribute if attrsNames is passed in.
         = (object) object of attributes if nothing is passed in.
         > Possible parameters
         # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p>
         o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`.
         o clip-rect (string) comma or space separated values: x, y, width and height
         o cursor (string) CSS type of the cursor
         o cx (number) the x-axis coordinate of the center of the circle, or ellipse
         o cy (number) the y-axis coordinate of the center of the circle, or ellipse
         o fill (string) colour, gradient or image
         o fill-opacity (number)
         o font (string)
         o font-family (string)
         o font-size (number) font size in pixels
         o font-weight (string)
         o height (number)
         o href (string) URL, if specified element behaves as hyperlink
         o opacity (number)
         o path (string) SVG path string format
         o r (number) radius of the circle, ellipse or rounded corner on the rect
         o rx (number) horisontal radius of the ellipse
         o ry (number) vertical radius of the ellipse
         o src (string) image URL, only works for @Element.image element
         o stroke (string) stroke colour
         o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”]
         o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”]
         o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”]
         o stroke-miterlimit (number)
         o stroke-opacity (number)
         o stroke-width (number) stroke width in pixels, default is '1'
         o target (string) used with href
         o text (string) contents of the text element. Use `\n` for multiline text
         o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`”
         o title (string) will create tooltip with a given text
         o transform (string) see @Element.transform
         o width (number)
         o x (number)
         o y (number)
         > Gradients
         * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90°
         * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black.
         *
         * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” –
         * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point
         * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses.
         > Path String
         # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p>
         > Colour Parsing
         # <ul>
         #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
         #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
         #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
         #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
         #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
         #     <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200,&nbsp;100,&nbsp;0, .5)</code>”)</li>
         #     <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%,&nbsp;175%,&nbsp;0%, 50%)</code>”)</li>
         #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
         #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li>
         #     <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li>
         #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
         #     <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li>
         #     <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg,&nbsp;1,&nbsp;.5)</code>” or, if you want to go fancy, “<code>hsl(240°,&nbsp;1,&nbsp;.5)</code>”</li>
         # </ul>
        \*/
        elproto.attr = function (name, value) {
            if (this.removed) {
                return this;
            }
            if (name == null) {
                var res = {};
                for (var a in this.attrs) if (this.attrs[has](a)) {
                    res[a] = this.attrs[a];
                }
                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
                res.transform = this._.transform;
                return res;
            }
            if (value == null && R.is(name, "string")) {
                if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
                    return this.attrs.gradient;
                }
                if (name == "transform") {
                    return this._.transform;
                }
                var names = name.split(separator),
                    out = {};
                for (var i = 0, ii = names.length; i < ii; i++) {
                    name = names[i];
                    if (name in this.attrs) {
                        out[name] = this.attrs[name];
                    } else if (R.is(this.paper.customAttributes[name], "function")) {
                        out[name] = this.paper.customAttributes[name].def;
                    } else {
                        out[name] = R._availableAttrs[name];
                    }
                }
                return ii - 1 ? out : out[names[0]];
            }
            if (value == null && R.is(name, "array")) {
                out = {};
                for (i = 0, ii = name.length; i < ii; i++) {
                    out[name[i]] = this.attr(name[i]);
                }
                return out;
            }
            if (value != null) {
                var params = {};
                params[name] = value;
            } else if (name != null && R.is(name, "object")) {
                params = name;
            }
            for (var key in params) {
                eve("raphael.attr." + key + "." + this.id, this, params[key]);
            }
            for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
                this.attrs[key] = params[key];
                for (var subkey in par) if (par[has](subkey)) {
                    params[subkey] = par[subkey];
                }
            }
            setFillAndStroke(this, params);
            return this;
        };
        /*\
         * Element.toFront
         [ method ]
         **
         * Moves the element so it is the closest to the viewer’s eyes, on top of other elements.
         = (object) @Element
        \*/
        elproto.toFront = function () {
            if (this.removed) {
                return this;
            }
            if (this.node.parentNode.tagName.toLowerCase() == "a") {
                this.node.parentNode.parentNode.appendChild(this.node.parentNode);
            } else {
                this.node.parentNode.appendChild(this.node);
            }
            var svg = this.paper;
            svg.top != this && R._tofront(this, svg);
            return this;
        };
        /*\
         * Element.toBack
         [ method ]
         **
         * Moves the element so it is the furthest from the viewer’s eyes, behind other elements.
         = (object) @Element
        \*/
        elproto.toBack = function () {
            if (this.removed) {
                return this;
            }
            var parent = this.node.parentNode;
            if (parent.tagName.toLowerCase() == "a") {
                parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
            } else if (parent.firstChild != this.node) {
                parent.insertBefore(this.node, this.node.parentNode.firstChild);
            }
            R._toback(this, this.paper);
            var svg = this.paper;
            return this;
        };
        /*\
         * Element.insertAfter
         [ method ]
         **
         * Inserts current object after the given one.
         = (object) @Element
        \*/
        elproto.insertAfter = function (element) {
            if (this.removed) {
                return this;
            }
            var node = element.node || element[element.length - 1].node;
            if (node.nextSibling) {
                node.parentNode.insertBefore(this.node, node.nextSibling);
            } else {
                node.parentNode.appendChild(this.node);
            }
            R._insertafter(this, element, this.paper);
            return this;
        };
        /*\
         * Element.insertBefore
         [ method ]
         **
         * Inserts current object before the given one.
         = (object) @Element
        \*/
        elproto.insertBefore = function (element) {
            if (this.removed) {
                return this;
            }
            var node = element.node || element[0].node;
            node.parentNode.insertBefore(this.node, node);
            R._insertbefore(this, element, this.paper);
            return this;
        };
        elproto.blur = function (size) {
            // Experimental. No Safari support. Use it on your own risk.
            var t = this;
            if (+size !== 0) {
                var fltr = $("filter"),
                    blur = $("feGaussianBlur");
                t.attrs.blur = size;
                fltr.id = R.createUUID();
                $(blur, {stdDeviation: +size || 1.5});
                fltr.appendChild(blur);
                t.paper.defs.appendChild(fltr);
                t._blur = fltr;
                $(t.node, {filter: "url(#" + fltr.id + ")"});
            } else {
                if (t._blur) {
                    t._blur.parentNode.removeChild(t._blur);
                    delete t._blur;
                    delete t.attrs.blur;
                }
                t.node.removeAttribute("filter");
            }
            return t;
        };
        R._engine.circle = function (svg, x, y, r) {
            var el = $("circle");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
            res.type = "circle";
            $(el, res.attrs);
            return res;
        };
        R._engine.rect = function (svg, x, y, w, h, r) {
            var el = $("rect");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
            res.type = "rect";
            $(el, res.attrs);
            return res;
        };
        R._engine.ellipse = function (svg, x, y, rx, ry) {
            var el = $("ellipse");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
            res.type = "ellipse";
            $(el, res.attrs);
            return res;
        };
        R._engine.image = function (svg, src, x, y, w, h) {
            var el = $("image");
            $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
            el.setAttributeNS(xlink, "href", src);
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {x: x, y: y, width: w, height: h, src: src};
            res.type = "image";
            return res;
        };
        R._engine.text = function (svg, x, y, text) {
            var el = $("text");
            svg.canvas && svg.canvas.appendChild(el);
            var res = new Element(el, svg);
            res.attrs = {
                x: x,
                y: y,
                "text-anchor": "middle",
                text: text,
                font: R._availableAttrs.font,
                stroke: "none",
                fill: "#000"
            };
            res.type = "text";
            setFillAndStroke(res, res.attrs);
            return res;
        };
        R._engine.setSize = function (width, height) {
            this.width = width || this.width;
            this.height = height || this.height;
            this.canvas.setAttribute("width", this.width);
            this.canvas.setAttribute("height", this.height);
            if (this._viewBox) {
                this.setViewBox.apply(this, this._viewBox);
            }
            return this;
        };
        R._engine.create = function () {
            var con = R._getContainer.apply(0, arguments),
                container = con && con.container,
                x = con.x,
                y = con.y,
                width = con.width,
                height = con.height;
            if (!container) {
                throw new Error("SVG container not found.");
            }
            var cnvs = $("svg"),
                css = "overflow:hidden;",
                isFloating;
            x = x || 0;
            y = y || 0;
            width = width || 512;
            height = height || 342;
            $(cnvs, {
                height: height,
                version: 1.1,
                width: width,
                xmlns: "http://www.w3.org/2000/svg"
            });
            if (container == 1) {
                cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
                R._g.doc.body.appendChild(cnvs);
                isFloating = 1;
            } else {
                cnvs.style.cssText = css + "position:relative";
                if (container.firstChild) {
                    container.insertBefore(cnvs, container.firstChild);
                } else {
                    container.appendChild(cnvs);
                }
            }
            container = new R._Paper;
            container.width = width;
            container.height = height;
            container.canvas = cnvs;
            container.clear();
            container._left = container._top = 0;
            isFloating && (container.renderfix = function () {});
            container.renderfix();
            return container;
        };
        R._engine.setViewBox = function (x, y, w, h, fit) {
            eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
            var size = mmax(w / this.width, h / this.height),
                top = this.top,
                aspectRatio = fit ? "meet" : "xMinYMin",
                vb,
                sw;
            if (x == null) {
                if (this._vbSize) {
                    size = 1;
                }
                delete this._vbSize;
                vb = "0 0 " + this.width + S + this.height;
            } else {
                this._vbSize = size;
                vb = x + S + y + S + w + S + h;
            }
            $(this.canvas, {
                viewBox: vb,
                preserveAspectRatio: aspectRatio
            });
            while (size && top) {
                sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
                top.attr({"stroke-width": sw});
                top._.dirty = 1;
                top._.dirtyT = 1;
                top = top.prev;
            }
            this._viewBox = [x, y, w, h, !!fit];
            return this;
        };
        /*\
         * Paper.renderfix
         [ method ]
         **
         * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant
         * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness.
         * This method fixes the issue.
         **
           Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method.
        \*/
        R.prototype.renderfix = function () {
            var cnvs = this.canvas,
                s = cnvs.style,
                pos;
            try {
                pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
            } catch (e) {
                pos = cnvs.createSVGMatrix();
            }
            var left = -pos.e % 1,
                top = -pos.f % 1;
            if (left || top) {
                if (left) {
                    this._left = (this._left + left) % 1;
                    s.left = this._left + "px";
                }
                if (top) {
                    this._top = (this._top + top) % 1;
                    s.top = this._top + "px";
                }
            }
        };
        /*\
         * Paper.clear
         [ method ]
         **
         * Clears the paper, i.e. removes all the elements.
        \*/
        R.prototype.clear = function () {
            R.eve("raphael.clear", this);
            var c = this.canvas;
            while (c.firstChild) {
                c.removeChild(c.firstChild);
            }
            this.bottom = this.top = null;
            (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
            c.appendChild(this.desc);
            c.appendChild(this.defs = $("defs"));
        };
        /*\
         * Paper.remove
         [ method ]
         **
         * Removes the paper from the DOM.
        \*/
        R.prototype.remove = function () {
            eve("raphael.remove", this);
            this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
        };
        var setproto = R.st;
        for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname].apply(el, arg);
                    });
                };
            })(method);
        }
    })();
    
    // ┌─────────────────────────────────────────────────────────────────────┐ \\
    // │ Raphaël - JavaScript Vector Library                                 │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ VML Module                                                          │ \\
    // ├─────────────────────────────────────────────────────────────────────┤ \\
    // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
    // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
    // │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
    // └─────────────────────────────────────────────────────────────────────┘ \\
    
    (function(){
        if (!R.vml) {
            return;
        }
        var has = "hasOwnProperty",
            Str = String,
            toFloat = parseFloat,
            math = Math,
            round = math.round,
            mmax = math.max,
            mmin = math.min,
            abs = math.abs,
            fillString = "fill",
            separator = /[, ]+/,
            eve = R.eve,
            ms = " progid:DXImageTransform.Microsoft",
            S = " ",
            E = "",
            map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
            bites = /([clmz]),?([^clmz]*)/gi,
            blurregexp = / progid:\S+Blur\([^\)]+\)/g,
            val = /-?[^,\s-]+/g,
            cssDot = "position:absolute;left:0;top:0;width:0px;height:0px",
            zoom = 21600,
            pathTypes = {path: 1, rect: 1, image: 1},
            ovalTypes = {circle: 1, ellipse: 1},
            path2vml = function (path) {
                var total =  /[ahqstv]/ig,
                    command = R._pathToAbsolute;
                Str(path).match(total) && (command = R._path2curve);
                total = /[clmz]/g;
                if (command == R._pathToAbsolute && !Str(path).match(total)) {
                    var res = Str(path).replace(bites, function (all, command, args) {
                        var vals = [],
                            isMove = command.toLowerCase() == "m",
                            res = map[command];
                        args.replace(val, function (value) {
                            if (isMove && vals.length == 2) {
                                res += vals + map[command == "m" ? "l" : "L"];
                                vals = [];
                            }
                            vals.push(round(value * zoom));
                        });
                        return res + vals;
                    });
                    return res;
                }
                var pa = command(path), p, r;
                res = [];
                for (var i = 0, ii = pa.length; i < ii; i++) {
                    p = pa[i];
                    r = pa[i][0].toLowerCase();
                    r == "z" && (r = "x");
                    for (var j = 1, jj = p.length; j < jj; j++) {
                        r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
                    }
                    res.push(r);
                }
                return res.join(S);
            },
            compensation = function (deg, dx, dy) {
                var m = R.matrix();
                m.rotate(-deg, .5, .5);
                return {
                    dx: m.x(dx, dy),
                    dy: m.y(dx, dy)
                };
            },
            setCoords = function (p, sx, sy, dx, dy, deg) {
                var _ = p._,
                    m = p.matrix,
                    fillpos = _.fillpos,
                    o = p.node,
                    s = o.style,
                    y = 1,
                    flip = "",
                    dxdy,
                    kx = zoom / sx,
                    ky = zoom / sy;
                s.visibility = "hidden";
                if (!sx || !sy) {
                    return;
                }
                o.coordsize = abs(kx) + S + abs(ky);
                s.rotation = deg * (sx * sy < 0 ? -1 : 1);
                if (deg) {
                    var c = compensation(deg, dx, dy);
                    dx = c.dx;
                    dy = c.dy;
                }
                sx < 0 && (flip += "x");
                sy < 0 && (flip += " y") && (y = -1);
                s.flip = flip;
                o.coordorigin = (dx * -kx) + S + (dy * -ky);
                if (fillpos || _.fillsize) {
                    var fill = o.getElementsByTagName(fillString);
                    fill = fill && fill[0];
                    o.removeChild(fill);
                    if (fillpos) {
                        c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
                        fill.position = c.dx * y + S + c.dy * y;
                    }
                    if (_.fillsize) {
                        fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy);
                    }
                    o.appendChild(fill);
                }
                s.visibility = "visible";
            };
        R.toString = function () {
            return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
        };
        var addArrow = function (o, value, isEnd) {
            var values = Str(value).toLowerCase().split("-"),
                se = isEnd ? "end" : "start",
                i = values.length,
                type = "classic",
                w = "medium",
                h = "medium";
            while (i--) {
                switch (values[i]) {
                    case "block":
                    case "classic":
                    case "oval":
                    case "diamond":
                    case "open":
                    case "none":
                        type = values[i];
                        break;
                    case "wide":
                    case "narrow": h = values[i]; break;
                    case "long":
                    case "short": w = values[i]; break;
                }
            }
            var stroke = o.node.getElementsByTagName("stroke")[0];
            stroke[se + "arrow"] = type;
            stroke[se + "arrowlength"] = w;
            stroke[se + "arrowwidth"] = h;
        },
        setFillAndStroke = function (o, params) {
            // o.paper.canvas.style.display = "none";
            o.attrs = o.attrs || {};
            var node = o.node,
                a = o.attrs,
                s = node.style,
                xy,
                newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
                isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
                res = o;
    
    
            for (var par in params) if (params[has](par)) {
                a[par] = params[par];
            }
            if (newpath) {
                a.path = R._getPath[o.type](o);
                o._.dirty = 1;
            }
            params.href && (node.href = params.href);
            params.title && (node.title = params.title);
            params.target && (node.target = params.target);
            params.cursor && (s.cursor = params.cursor);
            "blur" in params && o.blur(params.blur);
            if (params.path && o.type == "path" || newpath) {
                node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
                if (o.type == "image") {
                    o._.fillpos = [a.x, a.y];
                    o._.fillsize = [a.width, a.height];
                    setCoords(o, 1, 1, 0, 0, 0);
                }
            }
            "transform" in params && o.transform(params.transform);
            if (isOval) {
                var cx = +a.cx,
                    cy = +a.cy,
                    rx = +a.rx || +a.r || 0,
                    ry = +a.ry || +a.r || 0;
                node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
                o._.dirty = 1;
            }
            if ("clip-rect" in params) {
                var rect = Str(params["clip-rect"]).split(separator);
                if (rect.length == 4) {
                    rect[2] = +rect[2] + (+rect[0]);
                    rect[3] = +rect[3] + (+rect[1]);
                    var div = node.clipRect || R._g.doc.createElement("div"),
                        dstyle = div.style;
                    dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
                    if (!node.clipRect) {
                        dstyle.position = "absolute";
                        dstyle.top = 0;
                        dstyle.left = 0;
                        dstyle.width = o.paper.width + "px";
                        dstyle.height = o.paper.height + "px";
                        node.parentNode.insertBefore(div, node);
                        div.appendChild(node);
                        node.clipRect = div;
                    }
                }
                if (!params["clip-rect"]) {
                    node.clipRect && (node.clipRect.style.clip = "auto");
                }
            }
            if (o.textpath) {
                var textpathStyle = o.textpath.style;
                params.font && (textpathStyle.font = params.font);
                params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
                params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
                params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
                params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
            }
            if ("arrow-start" in params) {
                addArrow(res, params["arrow-start"]);
            }
            if ("arrow-end" in params) {
                addArrow(res, params["arrow-end"], 1);
            }
            if (params.opacity != null || 
                params["stroke-width"] != null ||
                params.fill != null ||
                params.src != null ||
                params.stroke != null ||
                params["stroke-width"] != null ||
                params["stroke-opacity"] != null ||
                params["fill-opacity"] != null ||
                params["stroke-dasharray"] != null ||
                params["stroke-miterlimit"] != null ||
                params["stroke-linejoin"] != null ||
                params["stroke-linecap"] != null) {
                var fill = node.getElementsByTagName(fillString),
                    newfill = false;
                fill = fill && fill[0];
                !fill && (newfill = fill = createNode(fillString));
                if (o.type == "image" && params.src) {
                    fill.src = params.src;
                }
                params.fill && (fill.on = true);
                if (fill.on == null || params.fill == "none" || params.fill === null) {
                    fill.on = false;
                }
                if (fill.on && params.fill) {
                    var isURL = Str(params.fill).match(R._ISURL);
                    if (isURL) {
                        fill.parentNode == node && node.removeChild(fill);
                        fill.rotate = true;
                        fill.src = isURL[1];
                        fill.type = "tile";
                        var bbox = o.getBBox(1);
                        fill.position = bbox.x + S + bbox.y;
                        o._.fillpos = [bbox.x, bbox.y];
    
                        R._preload(isURL[1], function () {
                            o._.fillsize = [this.offsetWidth, this.offsetHeight];
                        });
                    } else {
                        fill.color = R.getRGB(params.fill).hex;
                        fill.src = E;
                        fill.type = "solid";
                        if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
                            a.fill = "none";
                            a.gradient = params.fill;
                            fill.rotate = false;
                        }
                    }
                }
                if ("fill-opacity" in params || "opacity" in params) {
                    var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
                    opacity = mmin(mmax(opacity, 0), 1);
                    fill.opacity = opacity;
                    if (fill.src) {
                        fill.color = "none";
                    }
                }
                node.appendChild(fill);
                var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
                newstroke = false;
                !stroke && (newstroke = stroke = createNode("stroke"));
                if ((params.stroke && params.stroke != "none") ||
                    params["stroke-width"] ||
                    params["stroke-opacity"] != null ||
                    params["stroke-dasharray"] ||
                    params["stroke-miterlimit"] ||
                    params["stroke-linejoin"] ||
                    params["stroke-linecap"]) {
                    stroke.on = true;
                }
                (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
                var strokeColor = R.getRGB(params.stroke);
                stroke.on && params.stroke && (stroke.color = strokeColor.hex);
                opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
                var width = (toFloat(params["stroke-width"]) || 1) * .75;
                opacity = mmin(mmax(opacity, 0), 1);
                params["stroke-width"] == null && (width = a["stroke-width"]);
                params["stroke-width"] && (stroke.weight = width);
                width && width < 1 && (opacity *= width) && (stroke.weight = 1);
                stroke.opacity = opacity;
            
                params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
                stroke.miterlimit = params["stroke-miterlimit"] || 8;
                params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
                if (params["stroke-dasharray"]) {
                    var dasharray = {
                        "-": "shortdash",
                        ".": "shortdot",
                        "-.": "shortdashdot",
                        "-..": "shortdashdotdot",
                        ". ": "dot",
                        "- ": "dash",
                        "--": "longdash",
                        "- .": "dashdot",
                        "--.": "longdashdot",
                        "--..": "longdashdotdot"
                    };
                    stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
                }
                newstroke && node.appendChild(stroke);
            }
            if (res.type == "text") {
                res.paper.canvas.style.display = E;
                var span = res.paper.span,
                    m = 100,
                    fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
                s = span.style;
                a.font && (s.font = a.font);
                a["font-family"] && (s.fontFamily = a["font-family"]);
                a["font-weight"] && (s.fontWeight = a["font-weight"]);
                a["font-style"] && (s.fontStyle = a["font-style"]);
                fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
                s.fontSize = fontSize * m + "px";
                res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
                var brect = span.getBoundingClientRect();
                res.W = a.w = (brect.right - brect.left) / m;
                res.H = a.h = (brect.bottom - brect.top) / m;
                // res.paper.canvas.style.display = "none";
                res.X = a.x;
                res.Y = a.y + res.H / 2;
    
                ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
                var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
                for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
                    res._.dirty = 1;
                    break;
                }
            
                // text-anchor emulation
                switch (a["text-anchor"]) {
                    case "start":
                        res.textpath.style["v-text-align"] = "left";
                        res.bbx = res.W / 2;
                    break;
                    case "end":
                        res.textpath.style["v-text-align"] = "right";
                        res.bbx = -res.W / 2;
                    break;
                    default:
                        res.textpath.style["v-text-align"] = "center";
                        res.bbx = 0;
                    break;
                }
                res.textpath.style["v-text-kern"] = true;
            }
            // res.paper.canvas.style.display = E;
        },
        addGradientFill = function (o, gradient, fill) {
            o.attrs = o.attrs || {};
            var attrs = o.attrs,
                pow = Math.pow,
                opacity,
                oindex,
                type = "linear",
                fxfy = ".5 .5";
            o.attrs.gradient = gradient;
            gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
                type = "radial";
                if (fx && fy) {
                    fx = toFloat(fx);
                    fy = toFloat(fy);
                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
                    fxfy = fx + S + fy;
                }
                return E;
            });
            gradient = gradient.split(/\s*\-\s*/);
            if (type == "linear") {
                var angle = gradient.shift();
                angle = -toFloat(angle);
                if (isNaN(angle)) {
                    return null;
                }
            }
            var dots = R._parseDots(gradient);
            if (!dots) {
                return null;
            }
            o = o.shape || o.node;
            if (dots.length) {
                o.removeChild(fill);
                fill.on = true;
                fill.method = "none";
                fill.color = dots[0].color;
                fill.color2 = dots[dots.length - 1].color;
                var clrs = [];
                for (var i = 0, ii = dots.length; i < ii; i++) {
                    dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
                }
                fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
                if (type == "radial") {
                    fill.type = "gradientTitle";
                    fill.focus = "100%";
                    fill.focussize = "0 0";
                    fill.focusposition = fxfy;
                    fill.angle = 0;
                } else {
                    // fill.rotate= true;
                    fill.type = "gradient";
                    fill.angle = (270 - angle) % 360;
                }
                o.appendChild(fill);
            }
            return 1;
        },
        Element = function (node, vml) {
            this[0] = this.node = node;
            node.raphael = true;
            this.id = R._oid++;
            node.raphaelid = this.id;
            this.X = 0;
            this.Y = 0;
            this.attrs = {};
            this.paper = vml;
            this.matrix = R.matrix();
            this._ = {
                transform: [],
                sx: 1,
                sy: 1,
                dx: 0,
                dy: 0,
                deg: 0,
                dirty: 1,
                dirtyT: 1
            };
            !vml.bottom && (vml.bottom = this);
            this.prev = vml.top;
            vml.top && (vml.top.next = this);
            vml.top = this;
            this.next = null;
        };
        var elproto = R.el;
    
        Element.prototype = elproto;
        elproto.constructor = Element;
        elproto.transform = function (tstr) {
            if (tstr == null) {
                return this._.transform;
            }
            var vbs = this.paper._viewBoxShift,
                vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
                oldt;
            if (vbs) {
                oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
            }
            R._extractTransform(this, vbt + tstr);
            var matrix = this.matrix.clone(),
                skew = this.skew,
                o = this.node,
                split,
                isGrad = ~Str(this.attrs.fill).indexOf("-"),
                isPatt = !Str(this.attrs.fill).indexOf("url(");
            matrix.translate(1, 1);
            if (isPatt || isGrad || this.type == "image") {
                skew.matrix = "1 0 0 1";
                skew.offset = "0 0";
                split = matrix.split();
                if ((isGrad && split.noRotation) || !split.isSimple) {
                    o.style.filter = matrix.toFilter();
                    var bb = this.getBBox(),
                        bbt = this.getBBox(1),
                        dx = bb.x - bbt.x,
                        dy = bb.y - bbt.y;
                    o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
                    setCoords(this, 1, 1, dx, dy, 0);
                } else {
                    o.style.filter = E;
                    setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
                }
            } else {
                o.style.filter = E;
                skew.matrix = Str(matrix);
                skew.offset = matrix.offset();
            }
            oldt && (this._.transform = oldt);
            return this;
        };
        elproto.rotate = function (deg, cx, cy) {
            if (this.removed) {
                return this;
            }
            if (deg == null) {
                return;
            }
            deg = Str(deg).split(separator);
            if (deg.length - 1) {
                cx = toFloat(deg[1]);
                cy = toFloat(deg[2]);
            }
            deg = toFloat(deg[0]);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
                cx = bbox.x + bbox.width / 2;
                cy = bbox.y + bbox.height / 2;
            }
            this._.dirtyT = 1;
            this.transform(this._.transform.concat([["r", deg, cx, cy]]));
            return this;
        };
        elproto.translate = function (dx, dy) {
            if (this.removed) {
                return this;
            }
            dx = Str(dx).split(separator);
            if (dx.length - 1) {
                dy = toFloat(dx[1]);
            }
            dx = toFloat(dx[0]) || 0;
            dy = +dy || 0;
            if (this._.bbox) {
                this._.bbox.x += dx;
                this._.bbox.y += dy;
            }
            this.transform(this._.transform.concat([["t", dx, dy]]));
            return this;
        };
        elproto.scale = function (sx, sy, cx, cy) {
            if (this.removed) {
                return this;
            }
            sx = Str(sx).split(separator);
            if (sx.length - 1) {
                sy = toFloat(sx[1]);
                cx = toFloat(sx[2]);
                cy = toFloat(sx[3]);
                isNaN(cx) && (cx = null);
                isNaN(cy) && (cy = null);
            }
            sx = toFloat(sx[0]);
            (sy == null) && (sy = sx);
            (cy == null) && (cx = cy);
            if (cx == null || cy == null) {
                var bbox = this.getBBox(1);
            }
            cx = cx == null ? bbox.x + bbox.width / 2 : cx;
            cy = cy == null ? bbox.y + bbox.height / 2 : cy;
        
            this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
            this._.dirtyT = 1;
            return this;
        };
        elproto.hide = function () {
            !this.removed && (this.node.style.display = "none");
            return this;
        };
        elproto.show = function () {
            !this.removed && (this.node.style.display = E);
            return this;
        };
        elproto._getBBox = function () {
            if (this.removed) {
                return {};
            }
            return {
                x: this.X + (this.bbx || 0) - this.W / 2,
                y: this.Y - this.H,
                width: this.W,
                height: this.H
            };
        };
        elproto.remove = function () {
            if (this.removed || !this.node.parentNode) {
                return;
            }
            this.paper.__set__ && this.paper.__set__.exclude(this);
            R.eve.unbind("raphael.*.*." + this.id);
            R._tear(this, this.paper);
            this.node.parentNode.removeChild(this.node);
            this.shape && this.shape.parentNode.removeChild(this.shape);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            this.removed = true;
        };
        elproto.attr = function (name, value) {
            if (this.removed) {
                return this;
            }
            if (name == null) {
                var res = {};
                for (var a in this.attrs) if (this.attrs[has](a)) {
                    res[a] = this.attrs[a];
                }
                res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
                res.transform = this._.transform;
                return res;
            }
            if (value == null && R.is(name, "string")) {
                if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
                    return this.attrs.gradient;
                }
                var names = name.split(separator),
                    out = {};
                for (var i = 0, ii = names.length; i < ii; i++) {
                    name = names[i];
                    if (name in this.attrs) {
                        out[name] = this.attrs[name];
                    } else if (R.is(this.paper.customAttributes[name], "function")) {
                        out[name] = this.paper.customAttributes[name].def;
                    } else {
                        out[name] = R._availableAttrs[name];
                    }
                }
                return ii - 1 ? out : out[names[0]];
            }
            if (this.attrs && value == null && R.is(name, "array")) {
                out = {};
                for (i = 0, ii = name.length; i < ii; i++) {
                    out[name[i]] = this.attr(name[i]);
                }
                return out;
            }
            var params;
            if (value != null) {
                params = {};
                params[name] = value;
            }
            value == null && R.is(name, "object") && (params = name);
            for (var key in params) {
                eve("raphael.attr." + key + "." + this.id, this, params[key]);
            }
            if (params) {
                for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
                    var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
                    this.attrs[key] = params[key];
                    for (var subkey in par) if (par[has](subkey)) {
                        params[subkey] = par[subkey];
                    }
                }
                // this.paper.canvas.style.display = "none";
                if (params.text && this.type == "text") {
                    this.textpath.string = params.text;
                }
                setFillAndStroke(this, params);
                // this.paper.canvas.style.display = E;
            }
            return this;
        };
        elproto.toFront = function () {
            !this.removed && this.node.parentNode.appendChild(this.node);
            this.paper && this.paper.top != this && R._tofront(this, this.paper);
            return this;
        };
        elproto.toBack = function () {
            if (this.removed) {
                return this;
            }
            if (this.node.parentNode.firstChild != this.node) {
                this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
                R._toback(this, this.paper);
            }
            return this;
        };
        elproto.insertAfter = function (element) {
            if (this.removed) {
                return this;
            }
            if (element.constructor == R.st.constructor) {
                element = element[element.length - 1];
            }
            if (element.node.nextSibling) {
                element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
            } else {
                element.node.parentNode.appendChild(this.node);
            }
            R._insertafter(this, element, this.paper);
            return this;
        };
        elproto.insertBefore = function (element) {
            if (this.removed) {
                return this;
            }
            if (element.constructor == R.st.constructor) {
                element = element[0];
            }
            element.node.parentNode.insertBefore(this.node, element.node);
            R._insertbefore(this, element, this.paper);
            return this;
        };
        elproto.blur = function (size) {
            var s = this.node.runtimeStyle,
                f = s.filter;
            f = f.replace(blurregexp, E);
            if (+size !== 0) {
                this.attrs.blur = size;
                s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
                s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
            } else {
                s.filter = f;
                s.margin = 0;
                delete this.attrs.blur;
            }
            return this;
        };
    
        R._engine.path = function (pathString, vml) {
            var el = createNode("shape");
            el.style.cssText = cssDot;
            el.coordsize = zoom + S + zoom;
            el.coordorigin = vml.coordorigin;
            var p = new Element(el, vml),
                attr = {fill: "none", stroke: "#000"};
            pathString && (attr.path = pathString);
            p.type = "path";
            p.path = [];
            p.Path = E;
            setFillAndStroke(p, attr);
            vml.canvas.appendChild(el);
            var skew = createNode("skew");
            skew.on = true;
            el.appendChild(skew);
            p.skew = skew;
            p.transform(E);
            return p;
        };
        R._engine.rect = function (vml, x, y, w, h, r) {
            var path = R._rectPath(x, y, w, h, r),
                res = vml.path(path),
                a = res.attrs;
            res.X = a.x = x;
            res.Y = a.y = y;
            res.W = a.width = w;
            res.H = a.height = h;
            a.r = r;
            a.path = path;
            res.type = "rect";
            return res;
        };
        R._engine.ellipse = function (vml, x, y, rx, ry) {
            var res = vml.path(),
                a = res.attrs;
            res.X = x - rx;
            res.Y = y - ry;
            res.W = rx * 2;
            res.H = ry * 2;
            res.type = "ellipse";
            setFillAndStroke(res, {
                cx: x,
                cy: y,
                rx: rx,
                ry: ry
            });
            return res;
        };
        R._engine.circle = function (vml, x, y, r) {
            var res = vml.path(),
                a = res.attrs;
            res.X = x - r;
            res.Y = y - r;
            res.W = res.H = r * 2;
            res.type = "circle";
            setFillAndStroke(res, {
                cx: x,
                cy: y,
                r: r
            });
            return res;
        };
        R._engine.image = function (vml, src, x, y, w, h) {
            var path = R._rectPath(x, y, w, h),
                res = vml.path(path).attr({stroke: "none"}),
                a = res.attrs,
                node = res.node,
                fill = node.getElementsByTagName(fillString)[0];
            a.src = src;
            res.X = a.x = x;
            res.Y = a.y = y;
            res.W = a.width = w;
            res.H = a.height = h;
            a.path = path;
            res.type = "image";
            fill.parentNode == node && node.removeChild(fill);
            fill.rotate = true;
            fill.src = src;
            fill.type = "tile";
            res._.fillpos = [x, y];
            res._.fillsize = [w, h];
            node.appendChild(fill);
            setCoords(res, 1, 1, 0, 0, 0);
            return res;
        };
        R._engine.text = function (vml, x, y, text) {
            var el = createNode("shape"),
                path = createNode("path"),
                o = createNode("textpath");
            x = x || 0;
            y = y || 0;
            text = text || "";
            path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
            path.textpathok = true;
            o.string = Str(text);
            o.on = true;
            el.style.cssText = cssDot;
            el.coordsize = zoom + S + zoom;
            el.coordorigin = "0 0";
            var p = new Element(el, vml),
                attr = {
                    fill: "#000",
                    stroke: "none",
                    font: R._availableAttrs.font,
                    text: text
                };
            p.shape = el;
            p.path = path;
            p.textpath = o;
            p.type = "text";
            p.attrs.text = Str(text);
            p.attrs.x = x;
            p.attrs.y = y;
            p.attrs.w = 1;
            p.attrs.h = 1;
            setFillAndStroke(p, attr);
            el.appendChild(o);
            el.appendChild(path);
            vml.canvas.appendChild(el);
            var skew = createNode("skew");
            skew.on = true;
            el.appendChild(skew);
            p.skew = skew;
            p.transform(E);
            return p;
        };
        R._engine.setSize = function (width, height) {
            var cs = this.canvas.style;
            this.width = width;
            this.height = height;
            width == +width && (width += "px");
            height == +height && (height += "px");
            cs.width = width;
            cs.height = height;
            cs.clip = "rect(0 " + width + " " + height + " 0)";
            if (this._viewBox) {
                R._engine.setViewBox.apply(this, this._viewBox);
            }
            return this;
        };
        R._engine.setViewBox = function (x, y, w, h, fit) {
            R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
            var width = this.width,
                height = this.height,
                size = 1 / mmax(w / width, h / height),
                H, W;
            if (fit) {
                H = height / h;
                W = width / w;
                if (w * H < width) {
                    x -= (width - w * H) / 2 / H;
                }
                if (h * W < height) {
                    y -= (height - h * W) / 2 / W;
                }
            }
            this._viewBox = [x, y, w, h, !!fit];
            this._viewBoxShift = {
                dx: -x,
                dy: -y,
                scale: size
            };
            this.forEach(function (el) {
                el.transform("...");
            });
            return this;
        };
        var createNode;
        R._engine.initWin = function (win) {
                var doc = win.document;
                doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
                try {
                    !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
                    createNode = function (tagName) {
                        return doc.createElement('<rvml:' + tagName + ' class="rvml">');
                    };
                } catch (e) {
                    createNode = function (tagName) {
                        return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
                    };
                }
            };
        R._engine.initWin(R._g.win);
        R._engine.create = function () {
            var con = R._getContainer.apply(0, arguments),
                container = con.container,
                height = con.height,
                s,
                width = con.width,
                x = con.x,
                y = con.y;
            if (!container) {
                throw new Error("VML container not found.");
            }
            var res = new R._Paper,
                c = res.canvas = R._g.doc.createElement("div"),
                cs = c.style;
            x = x || 0;
            y = y || 0;
            width = width || 512;
            height = height || 342;
            res.width = width;
            res.height = height;
            width == +width && (width += "px");
            height == +height && (height += "px");
            res.coordsize = zoom * 1e3 + S + zoom * 1e3;
            res.coordorigin = "0 0";
            res.span = R._g.doc.createElement("span");
            res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
            c.appendChild(res.span);
            cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
            if (container == 1) {
                R._g.doc.body.appendChild(c);
                cs.left = x + "px";
                cs.top = y + "px";
                cs.position = "absolute";
            } else {
                if (container.firstChild) {
                    container.insertBefore(c, container.firstChild);
                } else {
                    container.appendChild(c);
                }
            }
            res.renderfix = function () {};
            return res;
        };
        R.prototype.clear = function () {
            R.eve("raphael.clear", this);
            this.canvas.innerHTML = E;
            this.span = R._g.doc.createElement("span");
            this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
            this.canvas.appendChild(this.span);
            this.bottom = this.top = null;
        };
        R.prototype.remove = function () {
            R.eve("raphael.remove", this);
            this.canvas.parentNode.removeChild(this.canvas);
            for (var i in this) {
                this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
            }
            return true;
        };
    
        var setproto = R.st;
        for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
            setproto[method] = (function (methodname) {
                return function () {
                    var arg = arguments;
                    return this.forEach(function (el) {
                        el[methodname].apply(el, arg);
                    });
                };
            })(method);
        }
    })();
    
        // EXPOSE
        // SVG and VML are appended just before the EXPOSE line
        // Even with AMD, Raphael should be defined globally
        oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
    
        return R;
    }));
    
    /** js sequence diagrams
     *  http://bramp.github.io/js-sequence-diagrams/
     *  (c) 2012-2013 Andrew Brampton (bramp.net)
     *  Simplified BSD license.
     */
    (function () {
    	
    	/*global grammar _ */
    
    	function Diagram() {
    		this.title   = undefined;
    		this.actors  = [];
    		this.signals = [];
    	}
    
    	Diagram.prototype.getActor = function(alias) {
    		var s = /^(.+) as (\S+)$/i.exec(alias.trim());
    		if (s) {
    			name  = s[1].trim();
    			alias = s[2].trim();
    		} else {
    			name = alias.trim();
    		}
    
    		name = name.replace(/\\n/gm, "\n");
    
    		var i, actors = this.actors;
    		for (i in actors) {
    			if (actors[i].alias == alias)
    				return actors[i];
    		}
    		i = actors.push( new Diagram.Actor(alias, name, actors.length) );
    		return actors[ i - 1 ];
    	};
    
    	Diagram.prototype.setTitle = function(title) {
    		this.title = title;
    	};
    
    	Diagram.prototype.addSignal = function(signal) {
    		this.signals.push( signal );
    	};
    
    	Diagram.Actor = function(alias, name, index) {
    		this.alias = alias;
    		this.name  = name;
    		this.index = index;
    	};
    
    	Diagram.Signal = function(actorA, signaltype, actorB, message) {
    		this.type       = "Signal";
    		this.actorA     = actorA;
    		this.actorB     = actorB;
    		this.linetype   = signaltype & 3;
    		this.arrowtype  = (signaltype >> 2) & 3;
    		this.message    = message;
    	};
    
    	Diagram.Signal.prototype.isSelf = function() {
    		return this.actorA.index == this.actorB.index;
    	};
    
    	Diagram.Note = function(actor, placement, message) {
    		this.type      = "Note";
    		this.actor     = actor;
    		this.placement = placement;
    		this.message   = message;
    
    		if (this.hasManyActors() && actor[0] == actor[1]) {
    			throw new Error("Note should be over two different actors");
    		}
    	};
    
    	Diagram.Note.prototype.hasManyActors = function() {
    		return _.isArray(this.actor);
    	};
    
    	Diagram.LINETYPE = {
    		SOLID  : 0,
    		DOTTED : 1
    	};
    
    	Diagram.ARROWTYPE = {
    		FILLED  : 0,
    		OPEN    : 1
    	};
    
    	Diagram.PLACEMENT = {
    		LEFTOF  : 0,
    		RIGHTOF : 1,
    		OVER    : 2
    	};
    
    	/** The following is included by jspp */
    	/* parser generated by jison 0.4.6 */
    /*
      Returns a Parser object of the following structure:
    
      Parser: {
        yy: {}
      }
    
      Parser.prototype: {
        yy: {},
        trace: function(),
        symbols_: {associative list: name ==> number},
        terminals_: {associative list: number ==> name},
        productions_: [...],
        performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
        table: [...],
        defaultActions: {...},
        parseError: function(str, hash),
        parse: function(input),
    
        lexer: {
            EOF: 1,
            parseError: function(str, hash),
            setInput: function(input),
            input: function(),
            unput: function(str),
            more: function(),
            less: function(n),
            pastInput: function(),
            upcomingInput: function(),
            showPosition: function(),
            test_match: function(regex_match_array, rule_index),
            next: function(),
            lex: function(),
            begin: function(condition),
            popState: function(),
            _currentRules: function(),
            topState: function(),
            pushState: function(condition),
    
            options: {
                ranges: boolean           (optional: true ==> token location info will include a .range[] member)
                flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
                backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
            },
    
            performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
            rules: [...],
            conditions: {associative list: name ==> set},
        }
      }
    
    
      token location info (@$, _$, etc.): {
        first_line: n,
        last_line: n,
        first_column: n,
        last_column: n,
        range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
      }
    
    
      the parseError function receives a 'hash' object with these members for lexer and parser errors: {
        text:        (matched text)
        token:       (the produced terminal token, if any)
        line:        (yylineno)
      }
      while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
        loc:         (yylloc)
        expected:    (string describing the set of expected tokens)
        recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
      }
    */
    var grammar = (function(){
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"start":3,"document":4,"EOF":5,"line":6,"statement":7,"NL":8,"participant":9,"actor":10,"signal":11,"note_statement":12,"title":13,"message":14,"note":15,"placement":16,"over":17,"actor_pair":18,",":19,"left_of":20,"right_of":21,"signaltype":22,"ACTOR":23,"linetype":24,"arrowtype":25,"LINE":26,"DOTLINE":27,"ARROW":28,"OPENARROW":29,"MESSAGE":30,"$accept":0,"$end":1},
    terminals_: {2:"error",5:"EOF",8:"NL",9:"participant",13:"title",15:"note",17:"over",19:",",20:"left_of",21:"right_of",23:"ACTOR",26:"LINE",27:"DOTLINE",28:"ARROW",29:"OPENARROW",30:"MESSAGE"},
    productions_: [0,[3,2],[4,0],[4,2],[6,1],[6,1],[7,2],[7,1],[7,1],[7,2],[12,4],[12,4],[18,1],[18,3],[16,1],[16,1],[11,4],[10,1],[22,2],[22,1],[24,1],[24,1],[25,1],[25,1],[14,1]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
    /* this == yyval */
    
    var $0 = $$.length - 1;
    switch (yystate) {
    case 1: return yy; 
    break;
    case 4: 
    break;
    case 6: $$[$0]; 
    break;
    case 7: yy.addSignal($$[$0]); 
    break;
    case 8: yy.addSignal($$[$0]); 
    break;
    case 9: yy.setTitle($$[$0]);  
    break;
    case 10: this.$ = new Diagram.Note($$[$0-1], $$[$0-2], $$[$0]); 
    break;
    case 11: this.$ = new Diagram.Note($$[$0-1], Diagram.PLACEMENT.OVER, $$[$0]); 
    break;
    case 12: this.$ = $$[$0]; 
    break;
    case 13: this.$ = [$$[$0-2], $$[$0]]; 
    break;
    case 14: this.$ = Diagram.PLACEMENT.LEFTOF; 
    break;
    case 15: this.$ = Diagram.PLACEMENT.RIGHTOF; 
    break;
    case 16: this.$ = new Diagram.Signal($$[$0-3], $$[$0-2], $$[$0-1], $$[$0]); 
    break;
    case 17: this.$ = yy.getActor($$[$0]); 
    break;
    case 18: this.$ = $$[$0-1] | ($$[$0] << 2); 
    break;
    case 19: this.$ = $$[$0]; 
    break;
    case 20: this.$ = Diagram.LINETYPE.SOLID; 
    break;
    case 21: this.$ = Diagram.LINETYPE.DOTTED; 
    break;
    case 22: this.$ = Diagram.ARROWTYPE.FILLED; 
    break;
    case 23: this.$ = Diagram.ARROWTYPE.OPEN; 
    break;
    case 24: this.$ = $$[$0].substring(1).trim().replace(/\\n/gm, "\n"); 
    break;
    }
    },
    table: [{3:1,4:2,5:[2,2],8:[2,2],9:[2,2],13:[2,2],15:[2,2],23:[2,2]},{1:[3]},{5:[1,3],6:4,7:5,8:[1,6],9:[1,7],10:11,11:8,12:9,13:[1,10],15:[1,12],23:[1,13]},{1:[2,1]},{5:[2,3],8:[2,3],9:[2,3],13:[2,3],15:[2,3],23:[2,3]},{5:[2,4],8:[2,4],9:[2,4],13:[2,4],15:[2,4],23:[2,4]},{5:[2,5],8:[2,5],9:[2,5],13:[2,5],15:[2,5],23:[2,5]},{10:14,23:[1,13]},{5:[2,7],8:[2,7],9:[2,7],13:[2,7],15:[2,7],23:[2,7]},{5:[2,8],8:[2,8],9:[2,8],13:[2,8],15:[2,8],23:[2,8]},{14:15,30:[1,16]},{22:17,24:18,26:[1,19],27:[1,20]},{16:21,17:[1,22],20:[1,23],21:[1,24]},{5:[2,17],8:[2,17],9:[2,17],13:[2,17],15:[2,17],19:[2,17],23:[2,17],26:[2,17],27:[2,17],30:[2,17]},{5:[2,6],8:[2,6],9:[2,6],13:[2,6],15:[2,6],23:[2,6]},{5:[2,9],8:[2,9],9:[2,9],13:[2,9],15:[2,9],23:[2,9]},{5:[2,24],8:[2,24],9:[2,24],13:[2,24],15:[2,24],23:[2,24]},{10:25,23:[1,13]},{23:[2,19],25:26,28:[1,27],29:[1,28]},{23:[2,20],28:[2,20],29:[2,20]},{23:[2,21],28:[2,21],29:[2,21]},{10:29,23:[1,13]},{10:31,18:30,23:[1,13]},{23:[2,14]},{23:[2,15]},{14:32,30:[1,16]},{23:[2,18]},{23:[2,22]},{23:[2,23]},{14:33,30:[1,16]},{14:34,30:[1,16]},{19:[1,35],30:[2,12]},{5:[2,16],8:[2,16],9:[2,16],13:[2,16],15:[2,16],23:[2,16]},{5:[2,10],8:[2,10],9:[2,10],13:[2,10],15:[2,10],23:[2,10]},{5:[2,11],8:[2,11],9:[2,11],13:[2,11],15:[2,11],23:[2,11]},{10:36,23:[1,13]},{30:[2,13]}],
    defaultActions: {3:[2,1],23:[2,14],24:[2,15],26:[2,18],27:[2,22],28:[2,23],36:[2,13]},
    parseError: function parseError(str, hash) {
        if (hash.recoverable) {
            this.trace(str);
        } else {
            throw new Error(str);
        }
    },
    parse: function parse(input) {
        var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
        this.lexer.setInput(input);
        this.lexer.yy = this.yy;
        this.yy.lexer = this.lexer;
        this.yy.parser = this;
        if (typeof this.lexer.yylloc == 'undefined') {
            this.lexer.yylloc = {};
        }
        var yyloc = this.lexer.yylloc;
        lstack.push(yyloc);
        var ranges = this.lexer.options && this.lexer.options.ranges;
        if (typeof this.yy.parseError === 'function') {
            this.parseError = this.yy.parseError;
        } else {
            this.parseError = Object.getPrototypeOf(this).parseError;
        }
        function popStack(n) {
            stack.length = stack.length - 2 * n;
            vstack.length = vstack.length - n;
            lstack.length = lstack.length - n;
        }
        function lex() {
            var token;
            token = self.lexer.lex() || EOF;
            if (typeof token !== 'number') {
                token = self.symbols_[token] || token;
            }
            return token;
        }
        var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
        while (true) {
            state = stack[stack.length - 1];
            if (this.defaultActions[state]) {
                action = this.defaultActions[state];
            } else {
                if (symbol === null || typeof symbol == 'undefined') {
                    symbol = lex();
                }
                action = table[state] && table[state][symbol];
            }
                        if (typeof action === 'undefined' || !action.length || !action[0]) {
                    var errStr = '';
                    expected = [];
                    for (p in table[state]) {
                        if (this.terminals_[p] && p > TERROR) {
                            expected.push('\'' + this.terminals_[p] + '\'');
                        }
                    }
                    if (this.lexer.showPosition) {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + this.lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
                    } else {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
                    }
                    this.parseError(errStr, {
                        text: this.lexer.match,
                        token: this.terminals_[symbol] || symbol,
                        line: this.lexer.yylineno,
                        loc: yyloc,
                        expected: expected
                    });
                }
            if (action[0] instanceof Array && action.length > 1) {
                throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
            }
            switch (action[0]) {
            case 1:
                stack.push(symbol);
                vstack.push(this.lexer.yytext);
                lstack.push(this.lexer.yylloc);
                stack.push(action[1]);
                symbol = null;
                if (!preErrorSymbol) {
                    yyleng = this.lexer.yyleng;
                    yytext = this.lexer.yytext;
                    yylineno = this.lexer.yylineno;
                    yyloc = this.lexer.yylloc;
                    if (recovering > 0) {
                        recovering--;
                    }
                } else {
                    symbol = preErrorSymbol;
                    preErrorSymbol = null;
                }
                break;
            case 2:
                len = this.productions_[action[1]][1];
                yyval.$ = vstack[vstack.length - len];
                yyval._$ = {
                    first_line: lstack[lstack.length - (len || 1)].first_line,
                    last_line: lstack[lstack.length - 1].last_line,
                    first_column: lstack[lstack.length - (len || 1)].first_column,
                    last_column: lstack[lstack.length - 1].last_column
                };
                if (ranges) {
                    yyval._$.range = [
                        lstack[lstack.length - (len || 1)].range[0],
                        lstack[lstack.length - 1].range[1]
                    ];
                }
                r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
                if (typeof r !== 'undefined') {
                    return r;
                }
                if (len) {
                    stack = stack.slice(0, -1 * len * 2);
                    vstack = vstack.slice(0, -1 * len);
                    lstack = lstack.slice(0, -1 * len);
                }
                stack.push(this.productions_[action[1]][0]);
                vstack.push(yyval.$);
                lstack.push(yyval._$);
                newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
                stack.push(newState);
                break;
            case 3:
                return true;
            }
        }
        return true;
    }};
    
    /* generated by jison-lex 0.2.1 */
    var lexer = (function(){
    var lexer = {
    
    EOF:1,
    
    parseError:function parseError(str, hash) {
            if (this.yy.parser) {
                this.yy.parser.parseError(str, hash);
            } else {
                throw new Error(str);
            }
        },
    
    // resets the lexer, sets new input
    setInput:function (input) {
            this._input = input;
            this._more = this._backtrack = this.done = false;
            this.yylineno = this.yyleng = 0;
            this.yytext = this.matched = this.match = '';
            this.conditionStack = ['INITIAL'];
            this.yylloc = {
                first_line: 1,
                first_column: 0,
                last_line: 1,
                last_column: 0
            };
            if (this.options.ranges) {
                this.yylloc.range = [0,0];
            }
            this.offset = 0;
            return this;
        },
    
    // consumes and returns one char from the input
    input:function () {
            var ch = this._input[0];
            this.yytext += ch;
            this.yyleng++;
            this.offset++;
            this.match += ch;
            this.matched += ch;
            var lines = ch.match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno++;
                this.yylloc.last_line++;
            } else {
                this.yylloc.last_column++;
            }
            if (this.options.ranges) {
                this.yylloc.range[1]++;
            }
    
            this._input = this._input.slice(1);
            return ch;
        },
    
    // unshifts one char (or a string) into the input
    unput:function (ch) {
            var len = ch.length;
            var lines = ch.split(/(?:\r\n?|\n)/g);
    
            this._input = ch + this._input;
            this.yytext = this.yytext.substr(0, this.yytext.length - len - 1);
            //this.yyleng -= len;
            this.offset -= len;
            var oldLines = this.match.split(/(?:\r\n?|\n)/g);
            this.match = this.match.substr(0, this.match.length - 1);
            this.matched = this.matched.substr(0, this.matched.length - 1);
    
            if (lines.length - 1) {
                this.yylineno -= lines.length - 1;
            }
            var r = this.yylloc.range;
    
            this.yylloc = {
                first_line: this.yylloc.first_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.first_column,
                last_column: lines ?
                    (lines.length === oldLines.length ? this.yylloc.first_column : 0)
                     + oldLines[oldLines.length - lines.length].length - lines[0].length :
                  this.yylloc.first_column - len
            };
    
            if (this.options.ranges) {
                this.yylloc.range = [r[0], r[0] + this.yyleng - len];
            }
            this.yyleng = this.yytext.length;
            return this;
        },
    
    // When called from action, caches matched text and appends it on next action
    more:function () {
            this._more = true;
            return this;
        },
    
    // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
    reject:function () {
            if (this.options.backtrack_lexer) {
                this._backtrack = true;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });
    
            }
            return this;
        },
    
    // retain first n characters of the match
    less:function (n) {
            this.unput(this.match.slice(n));
        },
    
    // displays already matched input, i.e. for error messages
    pastInput:function () {
            var past = this.matched.substr(0, this.matched.length - this.match.length);
            return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
        },
    
    // displays upcoming input, i.e. for error messages
    upcomingInput:function () {
            var next = this.match;
            if (next.length < 20) {
                next += this._input.substr(0, 20-next.length);
            }
            return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
        },
    
    // displays the character position where the lexing error occurred, i.e. for error messages
    showPosition:function () {
            var pre = this.pastInput();
            var c = new Array(pre.length + 1).join("-");
            return pre + this.upcomingInput() + "\n" + c + "^";
        },
    
    // test the lexed token: return FALSE when not a match, otherwise return token
    test_match:function (match, indexed_rule) {
            var token,
                lines,
                backup;
    
            if (this.options.backtrack_lexer) {
                // save context
                backup = {
                    yylineno: this.yylineno,
                    yylloc: {
                        first_line: this.yylloc.first_line,
                        last_line: this.last_line,
                        first_column: this.yylloc.first_column,
                        last_column: this.yylloc.last_column
                    },
                    yytext: this.yytext,
                    match: this.match,
                    matches: this.matches,
                    matched: this.matched,
                    yyleng: this.yyleng,
                    offset: this.offset,
                    _more: this._more,
                    _input: this._input,
                    yy: this.yy,
                    conditionStack: this.conditionStack.slice(0),
                    done: this.done
                };
                if (this.options.ranges) {
                    backup.yylloc.range = this.yylloc.range.slice(0);
                }
            }
    
            lines = match[0].match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno += lines.length;
            }
            this.yylloc = {
                first_line: this.yylloc.last_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.last_column,
                last_column: lines ?
                             lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
                             this.yylloc.last_column + match[0].length
            };
            this.yytext += match[0];
            this.match += match[0];
            this.matches = match;
            this.yyleng = this.yytext.length;
            if (this.options.ranges) {
                this.yylloc.range = [this.offset, this.offset += this.yyleng];
            }
            this._more = false;
            this._backtrack = false;
            this._input = this._input.slice(match[0].length);
            this.matched += match[0];
            token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
            if (this.done && this._input) {
                this.done = false;
            }
            if (token) {
                return token;
            } else if (this._backtrack) {
                // recover context
                for (var k in backup) {
                    this[k] = backup[k];
                }
                return false; // rule action called reject() implying the next rule should be tested instead.
            }
            return false;
        },
    
    // return next match in input
    next:function () {
            if (this.done) {
                return this.EOF;
            }
            if (!this._input) {
                this.done = true;
            }
    
            var token,
                match,
                tempMatch,
                index;
            if (!this._more) {
                this.yytext = '';
                this.match = '';
            }
            var rules = this._currentRules();
            for (var i = 0; i < rules.length; i++) {
                tempMatch = this._input.match(this.rules[rules[i]]);
                if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
                    match = tempMatch;
                    index = i;
                    if (this.options.backtrack_lexer) {
                        token = this.test_match(tempMatch, rules[i]);
                        if (token !== false) {
                            return token;
                        } else if (this._backtrack) {
                            match = false;
                            continue; // rule action called reject() implying a rule MISmatch.
                        } else {
                            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                            return false;
                        }
                    } else if (!this.options.flex) {
                        break;
                    }
                }
            }
            if (match) {
                token = this.test_match(match, rules[index]);
                if (token !== false) {
                    return token;
                }
                // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                return false;
            }
            if (this._input === "") {
                return this.EOF;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });
            }
        },
    
    // return next match that has a token
    lex:function lex() {
            var r = this.next();
            if (r) {
                return r;
            } else {
                return this.lex();
            }
        },
    
    // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
    begin:function begin(condition) {
            this.conditionStack.push(condition);
        },
    
    // pop the previously active lexer condition state off the condition stack
    popState:function popState() {
            var n = this.conditionStack.length - 1;
            if (n > 0) {
                return this.conditionStack.pop();
            } else {
                return this.conditionStack[0];
            }
        },
    
    // produce the lexer rule set which is active for the currently active lexer condition state
    _currentRules:function _currentRules() {
            if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
                return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
            } else {
                return this.conditions["INITIAL"].rules;
            }
        },
    
    // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
    topState:function topState(n) {
            n = this.conditionStack.length - 1 - Math.abs(n || 0);
            if (n >= 0) {
                return this.conditionStack[n];
            } else {
                return "INITIAL";
            }
        },
    
    // alias for begin(condition)
    pushState:function pushState(condition) {
            this.begin(condition);
        },
    
    // return the number of states currently on the stack
    stateStackSize:function stateStackSize() {
            return this.conditionStack.length;
        },
    options: {"case-insensitive":true},
    performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
    
    	// Pre-lexer code can go here
    
    var YYSTATE=YY_START;
    switch($avoiding_name_collisions) {
    case 0:return 8;
    break;
    case 1:/* skip whitespace */
    break;
    case 2:/* skip comments */
    break;
    case 3:return 9;
    break;
    case 4:return 20;
    break;
    case 5:return 21;
    break;
    case 6:return 17;
    break;
    case 7:return 15;
    break;
    case 8:return 13;
    break;
    case 9:return 19;
    break;
    case 10:return 23;
    break;
    case 11:return 27;
    break;
    case 12:return 26;
    break;
    case 13:return 29;
    break;
    case 14:return 28;
    break;
    case 15:return 30;
    break;
    case 16:return 5;
    break;
    case 17:return 'INVALID';
    break;
    }
    },
    rules: [/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:participant\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:title\b)/i,/^(?:,)/i,/^(?:[^\->:\n,]+)/i,/^(?:--)/i,/^(?:-)/i,/^(?:>>)/i,/^(?:>)/i,/^(?:[^#\n]+)/i,/^(?:$)/i,/^(?:.)/i],
    conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}}
    };
    return lexer;
    })();
    parser.lexer = lexer;
    function Parser () {
      this.yy = {};
    }
    Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();
    
    
    if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
    exports.parser = grammar;
    exports.Parser = grammar.Parser;
    exports.parse = function () { return grammar.parse.apply(grammar, arguments); };
    exports.main = function commonjsMain(args) {
        if (!args[1]) {
            console.log('Usage: '+args[0]+' FILE');
            process.exit(1);
        }
        var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
        return exports.parser.parse(source);
    };
    if (typeof module !== 'undefined' && require.main === module) {
      exports.main(process.argv.slice(1));
    }
    }
    
    	/**
    	 * jison doesn't have a good exception, so we make one
    	 */
    	function ParseError(message, hash) {
    		_.extend(this, hash);
    
    		this.name = "ParseError";
    		this.message = (message || "");
    	}
    	ParseError.prototype = new Error();
    	Diagram.ParseError = ParseError;
    
    	grammar.parseError = function(message, hash) {
    		throw new ParseError(message, hash);
    	};
    
    	Diagram.parse = function(input) {
    		grammar.yy = new Diagram();
    
    		return grammar.parse(input);
    	};
    
    	// Expose this class externally
    	this.Diagram = Diagram;
    
    }).call(this);
    define("diagram-grammar", function(){});
    
    /** js sequence diagrams
     *  http://bramp.github.io/js-sequence-diagrams/
     *  (c) 2012-2013 Andrew Brampton (bramp.net)
     *  Simplified BSD license.
     */
    (function(init) {
    	/*global Diagram, Raphael, _ */
    	if(typeof define === "function" && define.amd) {
    		define("Diagram", ['raphael'], function(Raphael) {
    			init(Raphael);
    			return Diagram;
    		});
    	}
    	else {
    		init(Raphael);
    	}
    })(function(Raphael) {
    	
    	/*global Diagram, Raphael, _ */
    
    	// Following the CSS convention
    	// Margin is the gap outside the box
    	// Padding is the gap inside the box
    	// Each object has x/y/width/height properties
    	// The x/y should be top left corner
    	// width/height is with both margin and padding
    
    	// TODO
    	// Image width is wrong, when there is a note in the right hand col
    	// Title box could look better
    	// Note box could look better
    
    	var DIAGRAM_MARGIN = 10;
    
    	var ACTOR_MARGIN   = 10; // Margin around a actor
    	var ACTOR_PADDING  = 10; // Padding inside a actor
    
    	var SIGNAL_MARGIN  = 5; // Margin around a signal
    	var SIGNAL_PADDING = 5; // Padding inside a signal
    
    	var NOTE_MARGIN   = 10; // Margin around a note
    	var NOTE_PADDING  = 5; // Padding inside a note
    	var NOTE_OVERLAP  = 15; // Overlap when using a "note over A,B"
    
    	var TITLE_MARGIN   = 0;
    	var TITLE_PADDING  = 5;
    
    	var SELF_SIGNAL_WIDTH = 20; // How far out a self signal goes
    
    	var PLACEMENT = Diagram.PLACEMENT;
    	var LINETYPE  = Diagram.LINETYPE;
    	var ARROWTYPE = Diagram.ARROWTYPE;
    
    	var LINE = {
    		'stroke': '#000',
    		'stroke-width': 2
    	};
    
    	var RECT = {
    		'fill': "#fff"
    	};
    
    	function AssertException(message) { this.message = message; }
    	AssertException.prototype.toString = function () {
    		return 'AssertException: ' + this.message;
    	};
    
    	function assert(exp, message) {
    		if (!exp) {
    			throw new AssertException(message);
    		}
    	}
    
    	if (!String.prototype.trim) {
    		String.prototype.trim=function() {
    			return this.replace(/^\s+|\s+$/g, '');
    		};
    	}
    
    /******************
     * Drawing extras
     ******************/
    
    	function getCenterX(box) {
    		return box.x + box.width / 2;
    	}
    
    	function getCenterY(box) {
    		return box.y + box.height / 2;
    	}
    
    /******************
     * Raphaël extras
     ******************/
    
    	Raphael.fn.line = function(x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    		return this.path("M{0},{1} L{2},{3}", x1, y1, x2, y2);
    	};
    
    	Raphael.fn.wobble = function(x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    
    		var wobble = Math.sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 25;
    
    		// Distance along line
    		var r1 = Math.random();
    		var r2 = Math.random();
    
    		var xfactor = Math.random() > 0.5 ? wobble : -wobble;
    		var yfactor = Math.random() > 0.5 ? wobble : -wobble;
    
    		var p1 = {
    			x: (x2 - x1) * r1 + x1 + xfactor,
    			y: (y2 - y1) * r1 + y1 + yfactor
    		};
    
    		var p2 = {
    			x: (x2 - x1) * r2 + x1 - xfactor,
    			y: (y2 - y1) * r2 + y1 - yfactor
    		};
    
    		return "C" + p1.x + "," + p1.y +
    			" " + p2.x + "," + p2.y +
    			" " + x2 + "," + y2;
    	};
    
    	/**
    	 * Returns the text's bounding box
    	 */
    	Raphael.fn.text_bbox = function (text, font) {
    		var p;
    		if (font._obj) {
    			p = this.print_center(0, 0, text, font._obj, font['font-size']);
    		} else {
    			p = this.text(0, 0, text);
    			p.attr(font);
    		}
    
    		var bb = p.getBBox();
    		p.remove();
    
    		return bb;
    	};
    
    	/**
    	 * Draws a wobbly (hand drawn) rect
    	 */
    	Raphael.fn.handRect = function (x, y, w, h) {
    		assert(_.all([x, y, w, h], _.isFinite), "x, y, w, h must be numeric");
    		return this.path("M" + x + "," + y +
    			this.wobble(x, y, x + w, y) +
    			this.wobble(x + w, y, x + w, y + h) +
    			this.wobble(x + w, y + h, x, y + h) +
    			this.wobble(x, y + h, x, y))
    			.attr(RECT);
    	};
    
    	/**
    	 * Draws a wobbly (hand drawn) line
    	 */
    	Raphael.fn.handLine = function (x1, y1, x2, y2) {
    		assert(_.all([x1,x2,y1,y2], _.isFinite), "x1,x2,y1,y2 must be numeric");
    		return this.path("M" + x1 + "," + y1 + this.wobble(x1, y1, x2, y2));
    	};
    
    	/**
    	 * Prints, but aligns text in a similar way to text(...)
    	 */
    	Raphael.fn.print_center = function(x, y, string, font, size, letter_spacing) {
    		var path = this.print(x, y, string, font, size, 'baseline', letter_spacing);
    		var bb = path.getBBox();
    
    		// Translate the text so it's centered.
    		var dx = (x - bb.x) - bb.width / 2;
    		var dy = (y - bb.y) - bb.height / 2;
    
    		// Due to an issue in Raphael 2.1.0 (that seems to be fixed later)
    		// we remap the path itself, instead of using a transformation matrix
    		var m = new Raphael.matrix();
    		m.translate(dx, dy);
    		return path.attr('path', Raphael.mapPath(path.attr('path'), m));
    
    		// otherwise we would do this:
    		//return path.transform("t" + dx + "," + dy);
    	};
    
    /******************
     * BaseTheme
     ******************/
    
    	var BaseTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	_.extend(BaseTheme.prototype, {
    		init : function(diagram) {
    			this.diagram = diagram;
    			this._paper  = undefined;
    			this._font   = undefined;
    
    			this._title  = undefined; // hack - This should be somewhere better
    
    			this._actors_height  = 0;
    			this._signals_height = 0;
    
    			var a = this.arrow_types = {};
    			a[ARROWTYPE.FILLED] = 'block';
    			a[ARROWTYPE.OPEN]   = 'open';
    
    			var l = this.line_types = {};
    			l[LINETYPE.SOLID]  = '';
    			l[LINETYPE.DOTTED] = '-';
    		},
    
    		init_paper : function(container) {
    			this._paper = new Raphael(container, 320, 200);
    		},
    
    		init_font : function() {},
    
    		draw_line : function(x1, y1, x2, y2) {
    			return this._paper.line(x1, y1, x2, y2);
    		},
    
    		draw_rect : function(x, y, w, h) {
    			return this._paper.rect(x, y, w, h);
    		},
    
    		draw : function(container) {
    			var diagram = this.diagram;
    			this.init_paper(container);
    			this.init_font();
    
    			this.layout();
    
    			var title_height = this._title ? this._title.height : 0;
    
    			this._paper.setStart();
    			this._paper.setSize(diagram.width, diagram.height);
    
    			var y = DIAGRAM_MARGIN + title_height;
    
    			this.draw_title();
    			this.draw_actors(y);
    			this.draw_signals(y + this._actors_height);
    
    			this._paper.setFinish();
    		},
    
    		layout : function() {
    			// Local copies
    			var diagram = this.diagram;
    			var paper   = this._paper;
    			var font    = this._font;
    			var actors  = diagram.actors;
    			var signals = diagram.signals;
    
    			diagram.width = 0;  // min width
    			diagram.height = 0; // min width
    
    			// Setup some layout stuff
    			if (diagram.title) {
    				var title = this._title = {};
    				var bb = paper.text_bbox(diagram.title, font);
    				title.text_bb = bb;
    				title.message = diagram.title;
    
    				title.width  = bb.width  + (TITLE_PADDING + TITLE_MARGIN) * 2;
    				title.height = bb.height + (TITLE_PADDING + TITLE_MARGIN) * 2;
    				title.x = DIAGRAM_MARGIN;
    				title.y = DIAGRAM_MARGIN;
    
    				diagram.width  += title.width;
    				diagram.height += title.height;
    			}
    
    			_.each(actors, function(a) {
    				var bb = paper.text_bbox(a.name, font);
    				a.text_bb = bb;
    
    				//var bb = t.attr("text", a.name).getBBox();
    				a.x = 0; a.y = 0;
    				a.width  = bb.width  + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
    				a.height = bb.height + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
    
    				a.distances = [];
    				a.padding_right = 0;
    				this._actors_height = Math.max(a.height, this._actors_height);
    			}, this);
    
    			function actor_ensure_distance(a, b, d) {
    				assert(a < b, "a must be less than or equal to b");
    
    				if (a < 0) {
    					// Ensure b has left margin
    					b = actors[b];
    					b.x = Math.max(d - b.width / 2, b.x);
    				} else if (b >= actors.length) {
    					// Ensure a has right margin
    					a = actors[a];
    					a.padding_right = Math.max(d, a.padding_right);
    				} else {
    					a = actors[a];
    					a.distances[b] = Math.max(d, a.distances[b] ? a.distances[b] : 0);
    				}
    			}
    
    			_.each(signals, function(s) {
    				var a, b; // Indexes of the left and right actors involved
    
    				var bb = paper.text_bbox(s.message, font);
    
    				//var bb = t.attr("text", s.message).getBBox();
    				s.text_bb = bb;
    				s.width   = bb.width;
    				s.height  = bb.height;
    
    				var extra_width = 0;
    
    				if (s.type == "Signal") {
    
    					s.width  += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
    					s.height += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
    
    					if (s.isSelf()) {
    						a = s.actorA.index;
    						b = a + 1;
    						s.width += SELF_SIGNAL_WIDTH;
    					} else {
    						a = Math.min(s.actorA.index, s.actorB.index);
    						b = Math.max(s.actorA.index, s.actorB.index);
    					}
    
    				} else if (s.type == "Note") {
    					s.width  += (NOTE_MARGIN + NOTE_PADDING) * 2;
    					s.height += (NOTE_MARGIN + NOTE_PADDING) * 2;
    
    					// HACK lets include the actor's padding
    					extra_width = 2 * ACTOR_MARGIN;
    
    					if (s.placement == PLACEMENT.LEFTOF) {
    						b = s.actor.index;
    						a = b - 1;
    					} else if (s.placement == PLACEMENT.RIGHTOF) {
    						a = s.actor.index;
    						b = a + 1;
    					} else if (s.placement == PLACEMENT.OVER && s.hasManyActors()) {
    						// Over multiple actors
    						a = Math.min(s.actor[0].index, s.actor[1].index);
    						b = Math.max(s.actor[0].index, s.actor[1].index);
    
    						// We don't need our padding, and we want to overlap
    						extra_width = - (NOTE_PADDING * 2 + NOTE_OVERLAP * 2);
    
    					} else if (s.placement == PLACEMENT.OVER) {
    						// Over single actor
    						a = s.actor.index;
    						actor_ensure_distance(a - 1, a, s.width / 2);
    						actor_ensure_distance(a, a + 1, s.width / 2);
    						this._signals_height += s.height;
    
    						return; // Bail out early
    					}
    				} else {
    					throw new Error("Unhandled signal type:" + s.type);
    				}
    
    				actor_ensure_distance(a, b, s.width + extra_width);
    				this._signals_height += s.height;
    			}, this);
    
    			// Re-jig the positions
    			var actors_x = 0;
    			_.each(actors, function(a) {
    				a.x = Math.max(actors_x, a.x);
    
    				// TODO This only works if we loop in sequence, 0, 1, 2, etc
    				_.each(a.distances, function(distance, b) {
    					// lodash (and possibly others) do not like sparse arrays
    					// so sometimes they return undefined
    					if (typeof distance == "undefined")
    						return;
    
    					b = actors[b];
    					distance = Math.max(distance, a.width / 2, b.width / 2);
    					b.x = Math.max(b.x, a.x + a.width/2 + distance - b.width/2);
    				});
    
    				actors_x = a.x + a.width + a.padding_right;
    			}, this);
    
    			diagram.width = Math.max(actors_x, diagram.width);
    
    			// TODO Refactor a little
    			diagram.width  += 2 * DIAGRAM_MARGIN;
    			diagram.height += 2 * DIAGRAM_MARGIN + 2 * this._actors_height + this._signals_height;
    
    			return this;
    		},
    
    		draw_title : function() {
    			var title = this._title;
    			if (title)
    				this.draw_text_box(title, title.message, TITLE_MARGIN, TITLE_PADDING, this._font);
    		},
    
    		draw_actors : function(offsetY) {
    			var y = offsetY;
    			_.each(this.diagram.actors, function(a) {
    				// Top box
    				this.draw_actor(a, y, this._actors_height);
    
    				// Bottom box
    				this.draw_actor(a, y + this._actors_height + this._signals_height, this._actors_height);
    
    				// Veritical line
    				var aX = getCenterX(a);
    				var line = this.draw_line(
    					aX, y + this._actors_height - ACTOR_MARGIN,
    					aX, y + this._actors_height + ACTOR_MARGIN + this._signals_height);
    				line.attr(LINE);
    			}, this);
    		},
    
    		draw_actor : function (actor, offsetY, height) {
    			actor.y      = offsetY;
    			actor.height = height;
    			this.draw_text_box(actor, actor.name, ACTOR_MARGIN, ACTOR_PADDING, this._font);
    		},
    
    		draw_signals : function (offsetY) {
    			var y = offsetY;
    			_.each(this.diagram.signals, function(s) {
    				if (s.type == "Signal") {
    					if (s.isSelf()) {
    						this.draw_self_signal(s, y);
    					} else {
    						this.draw_signal(s, y);
    					}
    
    				} else if (s.type == "Note") {
    					this.draw_note(s, y);
    				}
    
    				y += s.height;
    			}, this);
    		},
    
    		draw_self_signal : function(signal, offsetY) {
    			assert(signal.isSelf(), "signal must be a self signal");
    
    			var text_bb = signal.text_bb;
    			var aX = getCenterX(signal.actorA);
    
    			var x = aX + SELF_SIGNAL_WIDTH + SIGNAL_PADDING - text_bb.x;
    			var y = offsetY + signal.height / 2;
    
    			this.draw_text(x, y, signal.message, this._font);
    
    			var attr = _.extend({}, LINE, {
    				'stroke-dasharray': this.line_types[signal.linetype]
    			});
    
    			var y1 = offsetY + SIGNAL_MARGIN;
    			var y2 = y1 + signal.height - SIGNAL_MARGIN;
    
    			// Draw three lines, the last one with a arrow
    			var line;
    			line = this.draw_line(aX, y1, aX + SELF_SIGNAL_WIDTH, y1);
    			line.attr(attr);
    
    			line = this.draw_line(aX + SELF_SIGNAL_WIDTH, y1, aX + SELF_SIGNAL_WIDTH, y2);
    			line.attr(attr);
    
    			line = this.draw_line(aX + SELF_SIGNAL_WIDTH, y2, aX, y2);
    			attr['arrow-end'] = this.arrow_types[signal.arrowtype] + '-wide-long';
    			line.attr(attr);
    		},
    
    		draw_signal : function (signal, offsetY) {
    			var aX = getCenterX( signal.actorA );
    			var bX = getCenterX( signal.actorB );
    
    			// Mid point between actors
    			var x = (bX - aX) / 2 + aX;
    			var y = offsetY + SIGNAL_MARGIN + 2*SIGNAL_PADDING;
    
    			// Draw the text in the middle of the signal
    			this.draw_text(x, y, signal.message, this._font);
    
    			// Draw the line along the bottom of the signal
    			y = offsetY + signal.height - SIGNAL_MARGIN - SIGNAL_PADDING;
    			var line = this.draw_line(aX, y, bX, y);
    			line.attr(LINE);
    			line.attr({
    				'arrow-end': this.arrow_types[signal.arrowtype] + '-wide-long',
    				'stroke-dasharray': this.line_types[signal.linetype]
    			});
    
    			//var ARROW_SIZE = 16;
    			//var dir = this.actorA.x < this.actorB.x ? 1 : -1;
    			//draw_arrowhead(bX, offsetY, ARROW_SIZE, dir);
    		},
    
    		draw_note : function (note, offsetY) {
    			note.y = offsetY;
    			var actorA = note.hasManyActors() ? note.actor[0] : note.actor;
    			var aX = getCenterX( actorA );
    			switch (note.placement) {
    				case PLACEMENT.RIGHTOF:
    					note.x = aX + ACTOR_MARGIN;
    					break;
    				case PLACEMENT.LEFTOF:
    					note.x = aX - ACTOR_MARGIN - note.width;
    					break;
    				case PLACEMENT.OVER:
    					if (note.hasManyActors()) {
    						var bX = getCenterX( note.actor[1] );
    						var overlap = NOTE_OVERLAP + NOTE_PADDING;
    						note.x = aX - overlap;
    						note.width = (bX + overlap) - note.x;
    					} else {
    						note.x = aX - note.width / 2;
    					}
    					break;
    				default:
    					throw new Error("Unhandled note placement:" + note.placement);
    			}
    
    			this.draw_text_box(note, note.message, NOTE_MARGIN, NOTE_PADDING, this._font);
    		},
    
    		/**
    		 * Draws text with a white background
    		 * x,y (int) x,y center point for this text
    		 * TODO Horz center the text when it's multi-line print
    		 */
    		draw_text : function (x, y, text, font) {
    			var paper = this._paper;
    			var f = font || {};
    			var t;
    			if (f._obj) {
    				t = paper.print_center(x, y, text, f._obj, f['font-size']);
    			} else {
    				t = paper.text(x, y, text);
    				t.attr(f);
    			}
    			// draw a rect behind it
    			var bb = t.getBBox();
    			var r = paper.rect(bb.x, bb.y, bb.width, bb.height);
    			r.attr({'fill': "#fff", 'stroke': 'none'});
    
    			t.toFront();
    		},
    
    		draw_text_box : function (box, text, margin, padding, font) {
    			var x = box.x + margin;
    			var y = box.y + margin;
    			var w = box.width  - 2 * margin;
    			var h = box.height - 2 * margin;
    
    			// Draw inner box
    			var rect = this.draw_rect(x, y, w, h);
    			rect.attr(LINE);
    
    			// Draw text (in the center)
    			x = getCenterX(box);
    			y = getCenterY(box);
    
    			this.draw_text(x, y, text, font);
    		}
    
    		/**
    		 * Draws a arrow head
    		 * direction must be -1 for left, or 1 for right
    		 */
    		//function draw_arrowhead(x, y, size, direction) {
    		//	var dx = (size/2) * direction;
    		//	var dy = (size/2);
    		//
    		//	y -= dy; x -= dx;
    		//	var p = this._paper.path("M" + x + "," + y + "v" + size + "l" + dx + ",-" + (size/2) + "Z");
    		//}
    	});
    
    /******************
     * RaphaelTheme
     ******************/
    
    	var RaphaelTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	_.extend(RaphaelTheme.prototype, BaseTheme.prototype, {
    
    		init_font : function() {
    			this._font = {
    				'font-size': 16,
    				'font-family': 'Andale Mono, monospace'
    			};
    		}
    
    	});
    
    /******************
     * HandRaphaelTheme
     ******************/
    
    	var HandRaphaelTheme = function(diagram) {
    		this.init(diagram);
    	};
    
    	// Take the standard RaphaelTheme and make all the lines wobbly
    	_.extend(HandRaphaelTheme.prototype, BaseTheme.prototype, {
    		init_font : function() {
    			this._font = {
    				'font-size': 16,
    				'font-family': 'daniel'
    			};
    
    			this._font._obj = this._paper.getFont('daniel');
    		},
    
    		draw_line : function(x1, y1, x2, y2) {
    			return this._paper.handLine(x1, y1, x2, y2);
    		},
    
    		draw_rect : function(x, y, w, h) {
    			return this._paper.handRect(x, y, w, h);
    		}
    	});
    
    	var themes = {
    		simple : RaphaelTheme,
    		hand  : HandRaphaelTheme
    	};
    
    	Diagram.prototype.drawSVG = function (container, options) {
    		var default_options = {
    			theme: 'hand'
    		};
    
    		options = _.defaults(options || {}, default_options);
    
    		if (!(options.theme in themes))
    			throw new Error("Unsupported theme: " + options.theme);
    
    		var drawing = new themes[options.theme](this);
    		drawing.draw(container);
    
    	}; // end of drawSVG
    
    });
    
    // flowchart, v1.3.4
    // Copyright (c)2014 Adriano Raiano (adrai).
    // Distributed under MIT license
    // http://adrai.github.io/flowchart.js
    !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd&&define('flow-chart',[],b)}(this,function(){function a(b,c){if(!b||"function"==typeof b)return c;var d={};for(var e in c)d[e]=c[e];for(e in b)b[e]&&(d[e]="object"==typeof d[e]?a(d[e],b[e]):b[e]);return d}function b(a,b){if("function"==typeof Object.create)a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});else{a.super_=b;var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a}}function c(a,b,c){var d,e,f="M{0},{1}";for(d=2,e=2*c.length+2;e>d;d+=2)f+=" L{"+d+"},{"+(d+1)+"}";var g=[b.x,b.y];for(d=0,e=c.length;e>d;d++)g.push(c[d].x),g.push(c[d].y);var h=a.paper.path(f,g);h.attr("stroke",a.options["element-color"]),h.attr("stroke-width",a.options["line-width"]);var i=a.options.font,j=a.options["font-family"],k=a.options["font-weight"];return i&&h.attr({font:i}),j&&h.attr({"font-family":j}),k&&h.attr({"font-weight":k}),h}function d(a,b,c,d){var e,f;"[object Array]"!==Object.prototype.toString.call(c)&&(c=[c]);var g="M{0},{1}";for(e=2,f=2*c.length+2;f>e;e+=2)g+=" L{"+e+"},{"+(e+1)+"}";var h=[b.x,b.y];for(e=0,f=c.length;f>e;e++)h.push(c[e].x),h.push(c[e].y);var i=a.paper.path(g,h);i.attr({stroke:a.options["line-color"],"stroke-width":a.options["line-width"],"arrow-end":a.options["arrow-end"]});var j=a.options.font,k=a.options["font-family"],l=a.options["font-weight"];if(j&&i.attr({font:j}),k&&i.attr({"font-family":k}),l&&i.attr({"font-weight":l}),d){var m=!1,n=a.paper.text(0,0,d),o=!1,p=c[0];b.y===p.y&&(o=!0);var q=0,r=0;m?(q=b.x>p.x?b.x-(b.x-p.x)/2:p.x-(p.x-b.x)/2,r=b.y>p.y?b.y-(b.y-p.y)/2:p.y-(p.y-b.y)/2,o?(q-=n.getBBox().width/2,r-=a.options["text-margin"]):(q+=a.options["text-margin"],r-=n.getBBox().height/2)):(q=b.x,r=b.y,o?(q+=a.options["text-margin"]/2,r-=a.options["text-margin"]):(q+=a.options["text-margin"]/2,r+=a.options["text-margin"])),n.attr({"text-anchor":"start","font-size":a.options["font-size"],fill:a.options["font-color"],x:q,y:r}),j&&n.attr({font:j}),k&&n.attr({"font-family":k}),l&&n.attr({"font-weight":l})}return i}function e(a,b,c,d,e,f,g,h){var i,j,k,l,m,n={x:null,y:null,onLine1:!1,onLine2:!1};return i=(h-f)*(c-a)-(g-e)*(d-b),0===i?n:(j=b-f,k=a-e,l=(g-e)*j-(h-f)*k,m=(c-a)*j-(d-b)*k,j=l/i,k=m/i,n.x=a+j*(c-a),n.y=b+j*(d-b),j>0&&1>j&&(n.onLine1=!0),k>0&&1>k&&(n.onLine2=!0),n)}function f(a,b){b=b||{},this.paper=new Raphael(a),this.options=q.defaults(b,p),this.symbols=[],this.lines=[],this.start=null}function g(a,b,c){this.chart=a,this.group=this.chart.paper.set(),this.symbol=c,this.connectedTo=[],this.symbolType=b.symbolType,this.flowstate=b.flowstate||"future",this.next_direction=b.next&&b.direction_next?b.direction_next:void 0,this.text=this.chart.paper.text(0,0,b.text),b.key&&(this.text.node.id=b.key+"t"),this.text.node.setAttribute("class",this.getAttr("class")+"t"),this.text.attr({"text-anchor":"start",x:this.getAttr("text-margin"),fill:this.getAttr("font-color"),"font-size":this.getAttr("font-size")});var d=this.getAttr("font"),e=this.getAttr("font-family"),f=this.getAttr("font-weight");d&&this.text.attr({font:d}),e&&this.text.attr({"font-family":e}),f&&this.text.attr({"font-weight":f}),b.link&&this.text.attr("href",b.link),b.target&&this.text.attr("target",b.target);var g=this.getAttr("maxWidth");if(g){for(var h=b.text.split(" "),i="",j=0,k=h.length;k>j;j++){var l=h[j];this.text.attr("text",i+" "+l),i+=this.text.getBBox().width>g?"\n"+l:" "+l}this.text.attr("text",i.substring(1))}if(this.group.push(this.text),c){var m=this.getAttr("text-margin");c.attr({fill:this.getAttr("fill"),stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),width:this.text.getBBox().width+2*m,height:this.text.getBBox().height+2*m}),c.node.setAttribute("class",this.getAttr("class")),b.link&&c.attr("href",b.link),b.target&&c.attr("target",b.target),b.key&&(c.node.id=b.key),this.group.push(c),c.insertBefore(this.text),this.text.attr({y:c.getBBox().height/2}),this.initialize()}}function h(a,b){var c=a.paper.rect(0,0,0,0,20);b=b||{},b.text=b.text||"Start",g.call(this,a,b,c)}function i(a,b){var c=a.paper.rect(0,0,0,0,20);b=b||{},b.text=b.text||"End",g.call(this,a,b,c)}function j(a,b){var c=a.paper.rect(0,0,0,0);b=b||{},g.call(this,a,b,c)}function k(a,b){var c=a.paper.rect(0,0,0,0);b=b||{},g.call(this,a,b,c),c.attr({width:this.text.getBBox().width+4*this.getAttr("text-margin")}),this.text.attr({x:2*this.getAttr("text-margin")});var d=a.paper.rect(0,0,0,0);d.attr({x:this.getAttr("text-margin"),stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),width:this.text.getBBox().width+2*this.getAttr("text-margin"),height:this.text.getBBox().height+2*this.getAttr("text-margin"),fill:this.getAttr("fill")}),b.key&&(d.node.id=b.key+"i");var e=this.getAttr("font"),f=this.getAttr("font-family"),h=this.getAttr("font-weight");e&&d.attr({font:e}),f&&d.attr({"font-family":f}),h&&d.attr({"font-weight":h}),b.link&&d.attr("href",b.link),b.target&&d.attr("target",b.target),this.group.push(d),d.insertBefore(this.text),this.initialize()}function l(a,b){b=b||{},g.call(this,a,b),this.textMargin=this.getAttr("text-margin"),this.text.attr({x:3*this.textMargin});var d=this.text.getBBox().width+4*this.textMargin,e=this.text.getBBox().height+2*this.textMargin,f=this.textMargin,h=e/2,i={x:f,y:h},j=[{x:f-this.textMargin,y:e},{x:f-this.textMargin+d,y:e},{x:f-this.textMargin+d+2*this.textMargin,y:0},{x:f-this.textMargin+2*this.textMargin,y:0},{x:f,y:h}],k=c(a,i,j);k.attr({stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),fill:this.getAttr("fill")}),b.link&&k.attr("href",b.link),b.target&&k.attr("target",b.target),b.key&&(k.node.id=b.key),k.node.setAttribute("class",this.getAttr("class")),this.text.attr({y:k.getBBox().height/2}),this.group.push(k),k.insertBefore(this.text),this.initialize()}function m(a,b){b=b||{},g.call(this,a,b),this.textMargin=this.getAttr("text-margin"),this.yes_direction="bottom",this.no_direction="right",b.yes&&b.direction_yes&&b.no&&!b.direction_no?"right"===b.direction_yes?(this.no_direction="bottom",this.yes_direction="right"):(this.no_direction="right",this.yes_direction="bottom"):b.yes&&!b.direction_yes&&b.no&&b.direction_no?"right"===b.direction_no?(this.yes_direction="bottom",this.no_direction="right"):(this.yes_direction="right",this.no_direction="bottom"):(this.yes_direction="bottom",this.no_direction="right"),this.yes_direction=this.yes_direction||"bottom",this.no_direction=this.no_direction||"right",this.text.attr({x:2*this.textMargin});var d=this.text.getBBox().width+3*this.textMargin;d+=d/2;var e=this.text.getBBox().height+2*this.textMargin;e+=e/2,e=Math.max(.5*d,e);var f=d/4,h=e/4;this.text.attr({x:f+this.textMargin/2});var i={x:f,y:h},j=[{x:f-d/4,y:h+e/4},{x:f-d/4+d/2,y:h+e/4+e/2},{x:f-d/4+d,y:h+e/4},{x:f-d/4+d/2,y:h+e/4-e/2},{x:f-d/4,y:h+e/4}],k=c(a,i,j);k.attr({stroke:this.getAttr("element-color"),"stroke-width":this.getAttr("line-width"),fill:this.getAttr("fill")}),b.link&&k.attr("href",b.link),b.target&&k.attr("target",b.target),b.key&&(k.node.id=b.key),k.node.setAttribute("class",this.getAttr("class")),this.text.attr({y:k.getBBox().height/2}),this.group.push(k),k.insertBefore(this.text),this.initialize()}function n(a){function b(a){var b=a.indexOf("(")+1,c=a.indexOf(")");return b>=0&&c>=0?d.symbols[a.substring(0,b-1)]:d.symbols[a]}function c(a){var b="next",c=a.indexOf("(")+1,d=a.indexOf(")");return c>=0&&d>=0&&(b=D.substring(c,d),b.indexOf(",")<0&&"yes"!==b&&"no"!==b&&(b="next, "+b)),b}a=a||"",a=a.trim();for(var d={symbols:{},start:null,drawSVG:function(a,b){function c(a){if(g[a.key])return g[a.key];switch(a.symbolType){case"start":g[a.key]=new h(e,a);break;case"end":g[a.key]=new i(e,a);break;case"operation":g[a.key]=new j(e,a);break;case"inputoutput":g[a.key]=new l(e,a);break;case"subroutine":g[a.key]=new k(e,a);break;case"condition":g[a.key]=new m(e,a);break;default:return new Error("Wrong symbol type!")}return g[a.key]}var d=this;this.diagram&&this.diagram.clean();var e=new f(a,b);this.diagram=e;var g={};!function n(a,b,f){var g=c(a);return d.start===a?e.startWith(g):b&&f&&!b.pathOk&&(b instanceof m?(f.yes===a&&b.yes(g),f.no===a&&b.no(g)):b.then(g)),g.pathOk?g:(g instanceof m?(a.yes&&n(a.yes,g,a),a.no&&n(a.no,g,a)):a.next&&n(a.next,g,a),g)}(this.start),e.render()},clean:function(){this.diagram.clean()}},e=[],g=0,n=1,o=a.length;o>n;n++)if("\n"===a[n]&&"\\"!==a[n-1]){var p=a.substring(g,n);g=n+1,e.push(p.replace(/\\\n/g,"\n"))}g<a.length&&e.push(a.substr(g));for(var q=1,r=e.length;r>q;){var s=e[q];s.indexOf(": ")<0&&s.indexOf("(")<0&&s.indexOf(")")<0&&s.indexOf("->")<0&&s.indexOf("=>")<0?(e[q-1]+="\n"+s,e.splice(q,1),r--):q++}for(;e.length>0;){var t=e.splice(0,1)[0];if(t.indexOf("=>")>=0){var u,v=t.split("=>"),w={key:v[0],symbolType:v[1],text:null,link:null,target:null,flowstate:null};if(w.symbolType.indexOf(": ")>=0&&(u=w.symbolType.split(": "),w.symbolType=u[0],w.text=u[1]),w.text&&w.text.indexOf(":>")>=0?(u=w.text.split(":>"),w.text=u[0],w.link=u[1]):w.symbolType.indexOf(":>")>=0&&(u=w.symbolType.split(":>"),w.symbolType=u[0],w.link=u[1]),w.symbolType.indexOf("\n")>=0&&(w.symbolType=w.symbolType.split("\n")[0]),w.link){var x=w.link.indexOf("[")+1,y=w.link.indexOf("]");x>=0&&y>=0&&(w.target=w.link.substring(x,y),w.link=w.link.substring(0,x-1))}if(w.text&&w.text.indexOf("|")>=0){var z=w.text.split("|");w.text=z[0],w.flowstate=z[1].trim()}d.symbols[w.key]=w}else if(t.indexOf("->")>=0)for(var A=t.split("->"),B=0,C=A.length;C>B;B++){var D=A[B],E=b(D),F=c(D),G=null;if(F.indexOf(",")>=0){var H=F.split(",");F=H[0],G=H[1].trim()}if(d.start||(d.start=E),C>B+1){var I=A[B+1];E[F]=b(I),E["direction_"+F]=G,G=null}}}return d}Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),Array.prototype.lastIndexOf||(Array.prototype.lastIndexOf=function(a){if(null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=c;arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&d!=1/0&&d!=-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d))));for(var e=d>=0?Math.min(d,c-1):c-Math.abs(d);e>=0;e--)if(e in b&&b[e]===a)return e;return-1}),String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});var o={},p={x:0,y:0,"line-width":3,"line-length":50,"text-margin":10,"font-size":14,"font-color":"black","line-color":"black","element-color":"black",fill:"white","yes-text":"yes","no-text":"no","arrow-end":"block","class":"flowchart",symbols:{start:{},end:{},condition:{},inputoutput:{},operation:{},subroutine:{}}},q={defaults:a,inherits:b};return f.prototype.handle=function(a){this.symbols.indexOf(a)<=-1&&this.symbols.push(a);var b=this;return a instanceof m?(a.yes=function(c){return a.yes_symbol=c,a.no_symbol&&(a.pathOk=!0),b.handle(c)},a.no=function(c){return a.no_symbol=c,a.yes_symbol&&(a.pathOk=!0),b.handle(c)}):a.then=function(c){return a.next=c,a.pathOk=!0,b.handle(c)},a},f.prototype.startWith=function(a){return this.start=a,this.handle(a)},f.prototype.render=function(){var a,b=0,c=0,d=0,e=0,f=0,g=0;for(d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.width>b&&(b=a.width),a.height>c&&(c=a.height);for(d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.shiftX(this.options.x+(b-a.width)/2+this.options["line-width"]),a.shiftY(this.options.y+(c-a.height)/2+this.options["line-width"]);for(this.start.render(),d=0,e=this.symbols.length;e>d;d++)a=this.symbols[d],a.renderLines();for(f=this.maxXFromLine,d=0,e=this.symbols.length;e>d;d++){a=this.symbols[d];var h=a.getX()+a.width,i=a.getY()+a.height;h>f&&(f=h),i>g&&(g=i)}this.paper.setSize(f+this.options["line-width"],g+this.options["line-width"])},f.prototype.clean=function(){if(this.paper){var a=this.paper.canvas;a.parentNode.removeChild(a)}},g.prototype.getAttr=function(a){if(!this.chart)return void 0;var b,c=this.chart.options?this.chart.options[a]:void 0,d=this.chart.options.symbols?this.chart.options.symbols[this.symbolType][a]:void 0;return this.chart.options.flowstate&&this.chart.options.flowstate[this.flowstate]&&(b=this.chart.options.flowstate[this.flowstate][a]),b||d||c},g.prototype.initialize=function(){this.group.transform("t"+this.getAttr("line-width")+","+this.getAttr("line-width")),this.width=this.group.getBBox().width,this.height=this.group.getBBox().height},g.prototype.getCenter=function(){return{x:this.getX()+this.width/2,y:this.getY()+this.height/2}},g.prototype.getX=function(){return this.group.getBBox().x},g.prototype.getY=function(){return this.group.getBBox().y},g.prototype.shiftX=function(a){this.group.transform("t"+(this.getX()+a)+","+this.getY())},g.prototype.setX=function(a){this.group.transform("t"+a+","+this.getY())},g.prototype.shiftY=function(a){this.group.transform("t"+this.getX()+","+(this.getY()+a))},g.prototype.setY=function(a){this.group.transform("t"+this.getX()+","+a)},g.prototype.getTop=function(){var a=this.getY(),b=this.getX()+this.width/2;return{x:b,y:a}},g.prototype.getBottom=function(){var a=this.getY()+this.height,b=this.getX()+this.width/2;return{x:b,y:a}},g.prototype.getLeft=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX();return{x:b,y:a}},g.prototype.getRight=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.group.getBBox().width;return{x:b,y:a}},g.prototype.render=function(){if(this.next){var a=this.getAttr("line-length");if("right"===this.next_direction){var b=this.getRight();if(this.next.getLeft(),!this.next.isPositioned){this.next.setY(b.y-this.next.height/2),this.next.shiftX(this.group.getBBox().x+this.width+a);var c=this;!function e(){for(var b,d=!1,f=0,g=c.chart.symbols.length;g>f;f++){b=c.chart.symbols[f];var h=Math.abs(b.getCenter().x-c.next.getCenter().x);if(b.getCenter().y>c.next.getCenter().y&&h<=c.next.width/2){d=!0;break}}d&&(c.next.setX(b.getX()+b.width+a),e())}(),this.next.isPositioned=!0,this.next.render()}}else{var d=this.getBottom();this.next.getTop(),this.next.isPositioned||(this.next.shiftY(this.getY()+this.height+a),this.next.setX(d.x-this.next.width/2),this.next.isPositioned=!0,this.next.render())}}},g.prototype.renderLines=function(){this.next&&(this.next_direction?this.drawLineTo(this.next,"",this.next_direction):this.drawLineTo(this.next))},g.prototype.drawLineTo=function(a,b,c){this.connectedTo.indexOf(a)<0&&this.connectedTo.push(a);var f,g=this.getCenter().x,h=this.getCenter().y,i=(this.getTop(),this.getRight()),j=this.getBottom(),k=this.getLeft(),l=a.getCenter().x,m=a.getCenter().y,n=a.getTop(),o=a.getRight(),p=(a.getBottom(),a.getLeft()),q=g===l,r=h===m,s=m>h,t=h>m,u=g>l,v=l>g,w=0,x=this.getAttr("line-length"),y=this.getAttr("line-width");if(c&&"bottom"!==c||!q||!s)if(c&&"right"!==c||!r||!v)if(c&&"left"!==c||!r||!u)if(c&&"right"!==c||!q||!t)if(c&&"right"!==c||!q||!s)if(c&&"bottom"!==c||!u)if(c&&"bottom"!==c||!v)if(c&&"right"===c&&u)f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else if(c&&"right"===c&&v)f=d(this.chart,i,[{x:n.x,y:i.y},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else if(c&&"bottom"===c&&q&&t)f=d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:i.x+x/2,y:j.y+x/2},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+x/2;else if("left"===c&&q&&t){var z=k.x-x/2;p.x<k.x&&(z=p.x-x/2),f=d(this.chart,k,[{x:z,y:k.y},{x:z,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.leftStart=!0,a.topEnd=!0,w=k.x}else"left"===c&&(f=d(this.chart,k,[{x:n.x+(k.x-n.x)/2,y:k.y},{x:n.x+(k.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.leftStart=!0,a.topEnd=!0,w=k.x);else f=d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+(j.x-n.x)/2;else f=this.leftEnd&&t?d(this.chart,j,[{x:j.x,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:j.y+x/2},{x:j.x+(j.x-n.x)/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b):d(this.chart,j,[{x:j.x,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.bottomStart=!0,a.topEnd=!0,w=j.x+(j.x-n.x)/2;else f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else f=d(this.chart,i,[{x:i.x+x/2,y:i.y},{x:i.x+x/2,y:n.y-x/2},{x:n.x,y:n.y-x/2},{x:n.x,y:n.y}],b),this.rightStart=!0,a.topEnd=!0,w=i.x+x/2;else f=d(this.chart,k,o,b),this.leftStart=!0,a.rightEnd=!0,w=o.x;else f=d(this.chart,i,p,b),this.rightStart=!0,a.leftEnd=!0,w=p.x;else f=d(this.chart,j,n,b),this.bottomStart=!0,a.topEnd=!0,w=j.x;if(f){for(var A=0,B=this.chart.lines.length;B>A;A++)for(var C,D=this.chart.lines[A],E=D.attr("path"),F=f.attr("path"),G=0,H=E.length-1;H>G;G++){var I=[];I.push(["M",E[G][1],E[G][2]]),I.push(["L",E[G+1][1],E[G+1][2]]);for(var J=I[0][1],K=I[0][2],L=I[1][1],M=I[1][2],N=0,O=F.length-1;O>N;N++){var P=[];P.push(["M",F[N][1],F[N][2]]),P.push(["L",F[N+1][1],F[N+1][2]]);var Q=P[0][1],R=P[0][2],S=P[1][1],T=P[1][2],U=e(J,K,L,M,Q,R,S,T);if(U.onLine1&&U.onLine2){var V;R===T?Q>S?(V=["L",U.x+2*y,R],F.splice(N+1,0,V),V=["C",U.x+2*y,R,U.x,R-4*y,U.x-2*y,R],F.splice(N+2,0,V),f.attr("path",F)):(V=["L",U.x-2*y,R],F.splice(N+1,0,V),V=["C",U.x-2*y,R,U.x,R-4*y,U.x+2*y,R],F.splice(N+2,0,V),f.attr("path",F)):R>T?(V=["L",Q,U.y+2*y],F.splice(N+1,0,V),V=["C",Q,U.y+2*y,Q+4*y,U.y,Q,U.y-2*y],F.splice(N+2,0,V),f.attr("path",F)):(V=["L",Q,U.y-2*y],F.splice(N+1,0,V),V=["C",Q,U.y-2*y,Q+4*y,U.y,Q,U.y+2*y],F.splice(N+2,0,V),f.attr("path",F)),N+=2,C+=2}}}this.chart.lines.push(f)}(!this.chart.maxXFromLine||this.chart.maxXFromLine&&w>this.chart.maxXFromLine)&&(this.chart.maxXFromLine=w)},q.inherits(h,g),q.inherits(i,g),q.inherits(j,g),q.inherits(k,g),q.inherits(l,g),l.prototype.getLeft=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.textMargin;return{x:b,y:a}},l.prototype.getRight=function(){var a=this.getY()+this.group.getBBox().height/2,b=this.getX()+this.group.getBBox().width-this.textMargin;return{x:b,y:a}},q.inherits(m,g),m.prototype.render=function(){this.yes_direction&&(this[this.yes_direction+"_symbol"]=this.yes_symbol),this.no_direction&&(this[this.no_direction+"_symbol"]=this.no_symbol);var a=this.getAttr("line-length");if(this.bottom_symbol){var b=this.getBottom();this.bottom_symbol.getTop(),this.bottom_symbol.isPositioned||(this.bottom_symbol.shiftY(this.getY()+this.height+a),this.bottom_symbol.setX(b.x-this.bottom_symbol.width/2),this.bottom_symbol.isPositioned=!0,this.bottom_symbol.render())}if(this.right_symbol){var c=this.getRight();if(this.right_symbol.getLeft(),!this.right_symbol.isPositioned){this.right_symbol.setY(c.y-this.right_symbol.height/2),this.right_symbol.shiftX(this.group.getBBox().x+this.width+a);var d=this;!function e(){for(var b,c=!1,f=0,g=d.chart.symbols.length;g>f;f++){b=d.chart.symbols[f];var h=Math.abs(b.getCenter().x-d.right_symbol.getCenter().x);if(b.getCenter().y>d.right_symbol.getCenter().y&&h<=d.right_symbol.width/2){c=!0;break}}c&&(d.right_symbol.setX(b.getX()+b.width+a),e())}(),this.right_symbol.isPositioned=!0,this.right_symbol.render()}}},m.prototype.renderLines=function(){this.yes_symbol&&this.drawLineTo(this.yes_symbol,this.getAttr("yes-text"),this.yes_direction),this.no_symbol&&this.drawLineTo(this.no_symbol,this.getAttr("no-text"),this.no_direction)},o.parse=n,o});
    define('extensions/umlDiagrams',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	"text!html/umlDiagramsSettingsBlock.html",
    	'crel',
    	'Diagram',
    	'flow-chart'
    ], function(_, utils, logger, Extension, umlDiagramsSettingsBlockHTML, crel, Diagram, flowChart) {
    
    	var umlDiagrams = new Extension("umlDiagrams", "UML Diagrams", true);
    	umlDiagrams.settingsBlock = umlDiagramsSettingsBlockHTML;
    	umlDiagrams.defaultConfig = {
    		flowchartOptions: [
    			'{',
    			'   "line-width": 2,',
    			'   "font-family": "sans-serif",',
    			'   "font-weight": "normal"',
    			'}'
    		].join('\n')
    	};
    
    	umlDiagrams.onLoadSettings = function() {
    		utils.setInputValue("#textarea-umldiagram-flowchart-options", umlDiagrams.config.flowchartOptions);
    	};
    
    	umlDiagrams.onSaveSettings = function(newConfig, event) {
    		newConfig.flowchartOptions = utils.getInputJSONValue("#textarea-umldiagram-flowchart-options", event);
    	};
    
    	umlDiagrams.onPagedownConfigure = function(editor) {
    		var previewContentsElt = document.getElementById('preview-contents');
    		editor.hooks.chain("onPreviewRefresh", function() {
    			_.each(previewContentsElt.querySelectorAll('.prettyprint > .language-sequence'), function(elt) {
    				try {
    					var diagram = Diagram.parse(elt.textContent);
    					var preElt = elt.parentNode;
    					var containerElt = crel('div', {
    						class: 'sequence-diagram'
    					});
    					preElt.parentNode.replaceChild(containerElt, preElt);
    					diagram.drawSVG(containerElt, {
    						theme: 'simple'
    					});
    				}
    				catch(e) {
    				}
    			});
    			_.each(previewContentsElt.querySelectorAll('.prettyprint > .language-flow'), function(elt) {
    				try {
    					var chart = flowChart.parse(elt.textContent);
    					var preElt = elt.parentNode;
    					var containerElt = crel('div', {
    						class: 'flow-chart'
    					});
    					preElt.parentNode.replaceChild(containerElt, preElt);
    					chart.drawSVG(containerElt, JSON.parse(umlDiagrams.config.flowchartOptions));
    				}
    				catch(e) {
    				}
    			});
    		});
    	};
    
    	return umlDiagrams;
    });
    
    define('text!html/tocSettingsBlock.html',[],function () { return '<p>Generates a table of contents when a [TOC] marker is found.</p>\n<div class="form-horizontal">\n\t<div class="form-group">\n\t\t<label class="col-sm-4 control-label" for="input-toc-marker">Marker\n\t\t\tRegExp</label>\n\t\t<div class="col-sm-7">\n\t\t\n\t\t\t<input type="text" id="input-toc-marker" class="col-sm-4 form-control">\n\t\t</div>\n\t</div>\n\t<div class="form-group">\n\t\t<label class="col-sm-4 control-label" for="input-toc-maxdepth">Max depth</label>\n\t\t<div class="col-sm-7 form-inline">\n\t\t\t<input type="text" id="input-toc-maxdepth"\n\t\t\t\tclass="col-sm-5 form-control" placeholder="6">\n\t\t</div>\n\t</div>\n\t<div class="form-group">\n        <label class="col-sm-4 control-label" for="input-toc-button">Button over preview</label>\n        <div class="col-sm-7">\n        <div class="checkbox">\n            <input type="checkbox" id="input-toc-button">\n            </div>\n        </div>\n    </div>\n\t\n</div>';});
    
    define('extensions/toc',[
        // "jquery",
        "underscore",
        "utils",
        "classes/Extension",
        "text!html/tocSettingsBlock.html",
    ], function(_, utils, Extension, tocSettingsBlockHTML) {
    
        var toc = new Extension("toc", "Table of Contents", true);
        toc.settingsBlock = tocSettingsBlockHTML;
        toc.defaultConfig = {
            marker: "\\[(TOC|toc)\\]",
            maxDepth: 6,
            button: true,
        };
    
        toc.onLoadSettings = function() {
            utils.setInputValue("#input-toc-marker", toc.config.marker);
            utils.setInputValue("#input-toc-maxdepth", toc.config.maxDepth);
            utils.setInputChecked("#input-toc-button", toc.config.button);
        };
    
        toc.onSaveSettings = function(newConfig, event) {
            newConfig.marker = utils.getInputRegExpValue("#input-toc-marker", event);
            newConfig.maxDepth = utils.getInputIntValue("#input-toc-maxdepth");
            newConfig.button = utils.getInputChecked("#input-toc-button");
        };
    
        /*
        toc.onCreatePreviewButton = function() {
            if(toc.config.button) {
                return buttonTocHTML;
            }
        };
        */
    
        // TOC element description
        function TocElement(tagName, anchor, text) {
            this.tagName = tagName;
            this.anchor = anchor;
            this.text = text;
            this.children = [];
        }
        TocElement.prototype.childrenToString = function() {
            if(this.children.length === 0) {
                return "";
            }
            var result = "<ul>\n";
            _.each(this.children, function(child) {
                result += child.toString();
            });
            result += "</ul>\n";
            return result;
        };
        TocElement.prototype.toString = function() {
            var result = "<li>";
            if(this.anchor && this.text) {
                result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
            }
            result += this.childrenToString() + "</li>\n";
            return result;
        };
    
        // Transform flat list of TocElement into a tree
        function groupTags(array, level) {
            level = level || 1;
            var tagName = "H" + level;
            var result = [];
    
            var currentElement;
            function pushCurrentElement() {
                if(currentElement !== undefined) {
                    if(currentElement.children.length > 0) {
                        currentElement.children = groupTags(currentElement.children, level + 1);
                    }
                    result.push(currentElement);
                }
            }
    
            _.each(array, function(element) {
                if(element.tagName != tagName) {
                    if(level !== toc.config.maxDepth) {
                        if(currentElement === undefined) {
                            currentElement = new TocElement();
                        }
                        currentElement.children.push(element);
                    }
                }
                else {
                    pushCurrentElement();
                    currentElement = element;
                }
            });
            pushCurrentElement();
            return result;
        }
    
        // Build the TOC
        var previewContentsElt;
        function buildToc() {
            var anchorList = {};
            function createAnchor(element) {
                var id = element.id || utils.slugify(element.textContent) || 'title';
                var anchor = id;
                var index = 0;
                while (_.has(anchorList, anchor)) {
                    anchor = id + "-" + (++index);
                }
                anchorList[anchor] = true;
                // Update the id of the element
                element.id = anchor;
                return anchor;
            }
    
            var elementList = [];
            _.each(previewContentsElt.querySelectorAll('h1, h2, h3, h4, h5, h6'), function(elt) {
                elementList.push(new TocElement(elt.tagName, createAnchor(elt), elt.textContent));
            });
            elementList = groupTags(elementList);
            return '<div class="toc">\n<ul>\n' + elementList.join("") + '</ul>\n</div>\n';
        }
    
        toc.onPagedownConfigure = function(editor) {
            previewContentsElt = document.getElementById('preview-contents');
            var tocExp = new RegExp("^\\s*" + toc.config.marker + "\\s*$");
            // Run TOC generation when conversion is finished directly on HTML
            editor.hooks.chain("onPreviewRefresh", function() {
                var tocEltList = document.querySelectorAll('.table-of-contents, .toc');
                var htmlToc = buildToc();
                // Replace toc paragraphs
                _.each(previewContentsElt.getElementsByTagName('p'), function(elt) {
                    if(tocExp.test(elt.innerHTML)) {
                        elt.innerHTML = htmlToc;
                    }
                });
                // Add toc in the TOC button
                _.each(tocEltList, function(elt) {
                    elt.innerHTML = htmlToc;
                });
    
                $("#leanoteNavContentMd").height("auto"); // auto
                try {
                    if(!$(htmlToc).text()) {
                        $("#leanoteNavContentMd").html("&nbsp; &nbsp; Nothing...");
                    }
                } catch(e) {}
                // 这里, resize Height
                var curH = $("#leanoteNavContentMd").height();
                var pH = $("#mdEditor").height()-100;
                if(curH > pH) {
                    $("#leanoteNavContentMd").height(pH);
                }
            });
        };
    
        toc.onReady = function() {
            var isPreviewVisible = true;
            $(".preview-panel").on('hide.layout.toggle', function() {
                isPreviewVisible = false;
            }).on('shown.layout.toggle', function() {
                isPreviewVisible = true;
            });
            $('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
                !isPreviewVisible && evt.preventDefault();
            });
        };
    
        return toc;
    });
    
    define('extensions/emailConverter',[
        "classes/Extension",
    ], function(Extension) {
    
        var emailConverter = new Extension("emailConverter", "Markdown Email", true);
        emailConverter.settingsBlock = '<p>Converts email addresses in the form &lt;email@example.com&gt; into clickable links.</p>';
    
        emailConverter.onPagedownConfigure = function(editor) {
            editor.getConverter().hooks.chain("postConversion", function(text) {
                return text.replace(/<(mailto\:)?([^\s>]+@[^\s>]+\.\S+?)>/g, function(match, mailto, email) {
                    return '<a href="mailto:' + email + '">' + email + '</a>';
                });
            });
        };
    
        return emailConverter;
    });
    
    define('text!html/scrollSyncSettingsBlock.html',[],function () { return '<p>Binds together editor and preview scrollbars.</p>\n<blockquote>\n\t<p><b>Note:</b> The mapping between Markdown and HTML is based on the\n\tposition of the title elements (h1, h2...) in the page. Therefore if\n\tyour document does not contain any title, the mapping will be linear and\n\tconsequently less accurate.</p>\n</blockquote>';});
    
    define('extensions/scrollSync',[
    	// "jquery",
    	"underscore",
    	"classes/Extension",
    	"text!html/scrollSyncSettingsBlock.html"
    ], function(_, Extension, scrollSyncSettingsBlockHTML) {
    
    	var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true);
    	scrollSync.settingsBlock = scrollSyncSettingsBlockHTML;
    
    	var sectionList;
    	scrollSync.onSectionsCreated = function(sectionListParam) {
    		sectionList = sectionListParam;
    	};
    
    	var editorElt;
    	var previewElt;
    	var mdSectionList = [];
    	var htmlSectionList = [];
    	var lastEditorScrollTop;
    	var lastPreviewScrollTop;
    	var buildSections = _.debounce(function() {
    		mdSectionList = [];
    		var mdSectionOffset;
    		var scrollHeight;
    		_.each(editorElt.querySelectorAll(".wmd-input-section"), function(delimiterElt) {
    			if(mdSectionOffset === undefined) {
    				// Force start to 0 for the first section
    				mdSectionOffset = 0;
    				return;
    			}
    			delimiterElt = delimiterElt.firstChild;
    			// Consider div scroll position
    			var newSectionOffset = delimiterElt.offsetTop;
    			mdSectionList.push({
    				startOffset: mdSectionOffset,
    				endOffset: newSectionOffset,
    				height: newSectionOffset - mdSectionOffset
    			});
    			mdSectionOffset = newSectionOffset;
    		});
    		// Last section
    		scrollHeight = editorElt.scrollHeight;
    		mdSectionList.push({
    			startOffset: mdSectionOffset,
    			endOffset: scrollHeight,
    			height: scrollHeight - mdSectionOffset
    		});
    
    		// Find corresponding sections in the preview
    		htmlSectionList = [];
    		var htmlSectionOffset;
    		_.each(previewElt.querySelectorAll(".wmd-preview-section"), function(delimiterElt) {
    			if(htmlSectionOffset === undefined) {
    				// Force start to 0 for the first section
    				htmlSectionOffset = 0;
    				return;
    			}
    			// Consider div scroll position
    			var newSectionOffset = delimiterElt.offsetTop;
    			htmlSectionList.push({
    				startOffset: htmlSectionOffset,
    				endOffset: newSectionOffset,
    				height: newSectionOffset - htmlSectionOffset
    			});
    			htmlSectionOffset = newSectionOffset;
    		});
    		// Last section
    		scrollHeight = previewElt.scrollHeight;
    		htmlSectionList.push({
    			startOffset: htmlSectionOffset,
    			endOffset: scrollHeight,
    			height: scrollHeight - htmlSectionOffset
    		});
    
    		// apply Scroll Link (-10 to have a gap > 9px)
    		lastEditorScrollTop = -10;
    		lastPreviewScrollTop = -10;
    		doScrollSync();
    	}, 500);
    
    	var isPreviewVisible = true;
    	var isScrollEditor = false;
    	var isScrollPreview = false;
    	var isEditorMoving = false;
    	var isPreviewMoving = false;
    
    	function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) {
    		// Find the section corresponding to the offset
    		var sectionIndex;
    		var srcSection = _.find(srcSectionList, function(section, index) {
    			sectionIndex = index;
    			return srcScrollTop < section.endOffset;
    		});
    		if(srcSection === undefined) {
    			// Something very bad happened
    			return;
    		}
    		var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
    		var destSection = destSectionList[sectionIndex];
    		return destSection.startOffset + destSection.height * posInSection;
    	}
    
    	var timeoutId;
    	var currentEndCb;
    
    	function animate(elt, startValue, endValue, stepCb, endCb) {
    		if(currentEndCb) {
    			clearTimeout(timeoutId);
    			currentEndCb();
    		}
    		currentEndCb = endCb;
    		var diff = endValue - startValue;
    		var startTime = Date.now();
    
    		function tick() {
    			var currentTime = Date.now();
    			var progress = (currentTime - startTime) / 200;
    			if(progress < 1) {
    				var scrollTop = startValue + diff * Math.cos((1 - progress) * Math.PI / 2);
    				elt.scrollTop = scrollTop;
    				stepCb(scrollTop);
    				timeoutId = setTimeout(tick, 1);
    			}
    			else {
    				currentEndCb = undefined;
    				elt.scrollTop = endValue;
    				setTimeout(endCb, 100);
    			}
    		}
    
    		tick();
    	}
    
    	var doScrollSync = _.throttle(function() {
    		if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
    			return;
    		}
    		var editorScrollTop = editorElt.scrollTop;
    		editorScrollTop < 0 && (editorScrollTop = 0);
    		var previewScrollTop = previewElt.scrollTop;
    		var destScrollTop;
    		// Perform the animation if diff > 9px
    		if(isScrollEditor === true) {
    			if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) {
    				return;
    			}
    			isScrollEditor = false;
    			// Animate the preview
    			lastEditorScrollTop = editorScrollTop;
    			destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList);
    			destScrollTop = _.min([
    				destScrollTop,
    				previewElt.scrollHeight - previewElt.offsetHeight
    			]);
    			if(Math.abs(destScrollTop - previewScrollTop) <= 9) {
    				// Skip the animation if diff is <= 9
    				lastPreviewScrollTop = previewScrollTop;
    				return;
    			}
    			animate(previewElt, previewScrollTop, destScrollTop, function(currentScrollTop) {
    				isPreviewMoving = true;
    				lastPreviewScrollTop = currentScrollTop;
    			}, function() {
    				isPreviewMoving = false;
    			});
    		}
    		else if(isScrollPreview === true) {
    			if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) {
    				return;
    			}
    			isScrollPreview = false;
    			// Animate the editor
    			lastPreviewScrollTop = previewScrollTop;
    			destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
    			destScrollTop = _.min([
    				destScrollTop,
    				editorElt.scrollHeight - editorElt.offsetHeight
    			]);
    			if(Math.abs(destScrollTop - editorScrollTop) <= 9) {
    				// Skip the animation if diff is <= 9
    				lastEditorScrollTop = editorScrollTop;
    				return;
    			}
    			animate(editorElt, editorScrollTop, destScrollTop, function(currentScrollTop) {
    				isEditorMoving = true;
    				lastEditorScrollTop = currentScrollTop;
    			}, function() {
    				isEditorMoving = false;
    			});
    		}
    	}, 100);
    
    	scrollSync.onLayoutResize = function() {
    		isScrollEditor = true;
    		buildSections();
    	};
    
    	scrollSync.onFileClosed = function() {
    		mdSectionList = [];
    	};
    
    	var scrollAdjust = false;
    	scrollSync.onReady = function() {
    		previewElt = document.querySelector(".preview-container");
    		editorElt = document.querySelector("#wmd-input");
    
    		$(previewElt).scroll(function() {
    			if(isPreviewMoving === false && scrollAdjust === false) {
    				isScrollPreview = true;
    				isScrollEditor = false;
    				doScrollSync();
    			}
    			scrollAdjust = false;
    		});
    		$(editorElt).scroll(function() {
    			if(isEditorMoving === false) {
    				isScrollEditor = true;
    				isScrollPreview = false;
    				doScrollSync();
    			}
    		});
    
    		$(".preview-panel").on('hide.layout.toggle', function() {
    			isPreviewVisible = false;
    		}).on('shown.layout.toggle', function() {
    			isPreviewVisible = true;
    		});
    
    		// Reimplement anchor scrolling to work without preview
    		$('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
    			evt.preventDefault();
    			var id = this.hash;
    			var anchorElt = $(id);
    			if(!anchorElt.length) {
    				return;
    			}
    			var previewScrollTop = anchorElt[0].getBoundingClientRect().top - previewElt.getBoundingClientRect().top + previewElt.scrollTop;
    			previewElt.scrollTop = previewScrollTop;
    			var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
    			editorElt.scrollTop = editorScrollTop;
    		});
    	};
    
    	var previewContentsElt;
    	var previousHeight;
    	scrollSync.onPagedownConfigure = function(editor) {
    		previewContentsElt = document.getElementById("preview-contents");
    		editor.getConverter().hooks.chain("postConversion", function(text) {
    			// To avoid losing scrolling position before elements are fully loaded
    			previousHeight = previewContentsElt.offsetHeight;
    			previewContentsElt.style.height = previousHeight + 'px';
    			return text;
    		});
    	};
    
    	scrollSync.onPreviewFinished = function() {
    		// Now set the correct height
    		previewContentsElt.style.removeProperty('height');
    		var newHeight = previewContentsElt.offsetHeight;
    		isScrollEditor = true;
    		if(newHeight < previousHeight) {
    			// We expect a scroll adjustment
    			scrollAdjust = true;
    		}
    		buildSections();
    	};
    
    	return scrollSync;
    });
    
    define('text!extensions/shortcutsDefaultMapping.settings',[],function () { return '{\n    \'mod+b\': bindPagedownButton(\'bold\'),\n    \'mod+i\': bindPagedownButton(\'italic\'),\n    \'mod+l\': bindPagedownButton(\'link\'),\n    \'mod+q\': bindPagedownButton(\'quote\'),\n    \'mod+k\': bindPagedownButton(\'code\'),\n    \'mod+g\': bindPagedownButton(\'image\'),\n    \'mod+o\': bindPagedownButton(\'olist\'),\n    \'mod+u\': bindPagedownButton(\'ulist\'),\n    \'mod+h\': bindPagedownButton(\'heading\'),\n    \'mod+r\': bindPagedownButton(\'hr\'),\n    \'mod+z\': bindPagedownButton(\'undo\'),\n    \'mod+y\': bindPagedownButton(\'redo\'),\n    \'mod+shift+z\': bindPagedownButton(\'redo\'),\n    \'mod+m\': function(evt) {\n        $(\'.button-open-discussion\').click();\n        evt.preventDefault();\n    },\n    \'= = > space\': function() {\n        expand(\'==> \', \'⇒ \');\n    },\n    \'< = = space\': function() {\n        expand(\'<== \', \'⇐ \');\n    },\n    \'S t a c k E d i t\': function() {\n        eventMgr.onMessage("You are stunned!!! Aren\'t you?");\n    }\n}\n';});
    
    define('text!html/shortcutsSettingsBlock.html',[],function () { return '<p>Maps keyboard shortcuts to JavaScript functions.</p>\n<div class="form-horizontal">\n\t<div class="form-group">\n\t\t<label class="col-sm-3 control-label" for="textarea-shortcuts-mapping">Mapping\n\t\t\t<a href="#" class="tooltip-shortcuts-extension">(?)</a>\n\t\t</label>\n\t\t<div class="col-sm-8">\n\t\t\t<textarea id="textarea-shortcuts-mapping" class="form-control"></textarea>\n\t\t</div>\n\t</div>\n</div>\n';});
    
    define('text!html/tooltipSettingsShortcutsExtension.html',[],function () { return 'You can create expanding macros like this:\n<br />\n<br />\n\'- - > space\': function() {\n<br />\n\texpand(\'--> \', \'→ \');\n<br />\n}\n<br />\n<br />\n<a target="_blank"\n\thref="http://craig.is/killing/mice">More\n\tinfo</a>\n<br />\n<br />\n<b class="text-danger"><i class="icon-attention"></i> Careful! This is subject to malicious code. Don\'t copy/paste untrusted content.</b>\n';});
    
    define('extensions/shortcuts',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"mousetrap",
    	"classes/Extension",
    	"text!extensions/shortcutsDefaultMapping.settings",
    	"text!html/shortcutsSettingsBlock.html",
    	"text!html/tooltipSettingsShortcutsExtension.html"
    ], function(_, utils, mousetrap, Extension, shortcutsDefaultMapping, shortcutsSettingsBlockHTML, tooltipSettingsShortcutsExtensionHTML) {
    
    	var shortcuts = new Extension("shortcuts", "Shortcuts", true, true);
    	shortcuts.settingsBlock = shortcutsSettingsBlockHTML;
    	shortcuts.defaultConfig = {
    		mapping: shortcutsDefaultMapping
    	};
    
    	var eventMgr;
    	var pagedownEditor;
    	shortcuts.onEventMgrCreated = function(eventMgrParameter) {
    		eventMgr = eventMgrParameter;
    		eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
    			pagedownEditor = pagedownEditorParam;
    		});
    	};
    
    	shortcuts.onLoadSettings = function() {
    		utils.setInputValue("#textarea-shortcuts-mapping", shortcuts.config.mapping);
    	};
    
    	shortcuts.onSaveSettings = function(newConfig, event) {
    		newConfig.code = utils.getInputValue("#textarea-shortcuts-mapping");
    		try {
    			/*jshint evil: true */
    			eval('var test = ' + newConfig.code);
    		}
    		catch(e) {
    			eventMgr.onError(e);
    			// Mark the textarea as error
    			utils.getInputTextValue("#textarea-shortcuts-mapping", event, /^$/);
    		}
    	};
    
    	/*jshint unused:false */
    	function bindPagedownButton(buttonName) {
    		return function(evt) {
    			pagedownEditor.uiManager.doClick(pagedownEditor.uiManager.buttons[buttonName]);
    			evt.preventDefault();
    		};
    	}
    
    	function expand(text, replacement) {
    		utils.defer(function() {
    			require('editor').replacePreviousText(text, replacement);
    		});
    	}
    
    	/*
    	
        'mod+b': bindPagedownButton('bold'),
        'mod+i': bindPagedownButton('italic'),
        'mod+l': bindPagedownButton('link'),
        'mod+q': bindPagedownButton('quote'),
        'mod+k': bindPagedownButton('code'),
        'mod+g': bindPagedownButton('image'),
        'mod+o': bindPagedownButton('olist'),
        'mod+u': bindPagedownButton('ulist'),
        'mod+h': bindPagedownButton('heading'),
        'mod+r': bindPagedownButton('hr'),
        'mod+z': bindPagedownButton('undo'),
        'mod+y': bindPagedownButton('redo'),
        'mod+shift+z': bindPagedownButton('redo'),
        'mod+m': function(evt) {
            $('.button-open-discussion').click();
            evt.preventDefault();
        },
        '= = > space': function() {
            expand('==> ', '⇒ ');
        },
        '< = = space': function() {
            expand('<== ', '⇐ ');
        },
        'S t a c k E d i t': function() {
            eventMgr.onMessage("You are stunned!!! Aren't you?");
        }
    	*/
    	/*jshint unused:true */
    	shortcuts.onInit = function() {
    		try {
    			// http://craig.is/killing/mice
    			// 只在输入框下有效, life
    			// http://stackoverflow.com/questions/16169645/using-mousetrap-on-a-specific-element
    			var input = $('.editor-content');
    			mousetrap.stopCallback = function(e, element, combo) {
    				return element !== input[0];
    			};
    
    			var shortcutMap;
    			/*jshint evil: true */
    			eval('shortcutMap = ' + shortcuts.config.mapping);
    			_.each(shortcutMap, function(func, shortcut) {
    				mousetrap.bind(shortcut, func);
    			});
    		}
    		catch(e) {
    			console.error(e);
    		}
    	};
    
    	shortcuts.onReady = function() {
    		// utils.createTooltip(".tooltip-shortcuts-extension", tooltipSettingsShortcutsExtensionHTML);
    	};
    
    	return shortcuts;
    });
    
    define('text!html/findReplace.html',[],function () { return '<button type="button" class="close button-find-replace-dismiss">×</button>\n<div class="form-inline">\n    <div class="form-group">\n        <label for="input-find-replace-search-for">Search for</label>\n        <input class="form-control" id="input-find-replace-search-for" placeholder="Search for">\n    </div>\n    <div class="form-group">\n        <label for="input-find-replace-replace-with">Replace with</label>\n        <input class="form-control" id="input-find-replace-replace-with" placeholder="Replace with">\n    </div>\n</div>\n<div class="pull-right">\n    <div class="help-block text-right">\n        <span class="found-counter">0</span> found\n    </div>\n    <div>\n        <button type="button" class="btn btn-primary search-button">Search</button>\n        <button type="button" class="btn btn-default replace-button">Replace</button>\n        <button type="button" class="btn btn-default replace-all-button">All</button>\n    </div>\n</div>\n<div class="pull-left">\n    <div class="checkbox">\n        <label>\n            <input type="checkbox" class="checkbox-case-sensitive"> Case sensitive\n        </label>\n    </div>\n    <div class="checkbox">\n        <label>\n            <input type="checkbox" class="checkbox-regexp"> Regular expression\n        </label>\n    </div>\n</div>\n';});
    
    define('text!html/findReplaceSettingsBlock.html',[],function () { return '<p>Helps find and replace text in the current document.</p>\n<div class="form-horizontal">\n\t<div class="form-group">\n\t\t<label class="col-sm-5 control-label"\n\t\t\tfor="input-find-replace-shortcut">Shortcut <a href="http://craig.is/killing/mice#keys" target="_blank">(?)</a></label>\n\t\t<div class="col-sm-6">\n\t\t\t<input type="text" id="input-find-replace-shortcut"\n\t\t\t\tclass="form-control">\n\t\t</div>\n\t</div>\n</div>';});
    
    define('extensions/findReplace',[
    	// "jquery",
    	"underscore",
    	"crel",
    	"utils",
    	"classes/Extension",
    	"mousetrap",
    	"rangy",
    	"text!html/findReplace.html",
    	"text!html/findReplaceSettingsBlock.html"
    ], function(_, crel, utils, Extension, mousetrap, rangy, findReplaceHTML, findReplaceSettingsBlockHTML) {
    
    	var findReplace = new Extension("findReplace", 'Find and Replace', true, true);
    	findReplace.settingsBlock = findReplaceSettingsBlockHTML;
    	findReplace.defaultConfig = {
    		findReplaceShortcut: 'mod+f'
    	};
    
    	findReplace.onLoadSettings = function() {
    		utils.setInputValue("#input-find-replace-shortcut", findReplace.config.findReplaceShortcut);
    	};
    
    	findReplace.onSaveSettings = function(newConfig, event) {
    		newConfig.findReplaceShortcut = utils.getInputTextValue("#input-find-replace-shortcut", event);
    	};
    
    	var editor;
    	findReplace.onEditorCreated = function(editorParam) {
    		editor = editorParam;
    	};
    
    	var eventMgr;
    	findReplace.onEventMgrCreated = function(eventMgrParam) {
    		eventMgr = eventMgrParam;
    	};
    
    	var rangeList = [];
    	var offsetList = [];
    	var highlightCssApplier, selectCssApplier;
    	var selectRange;
    	function resetHighlight() {
    		resetSelect();
    		rangeList.forEach(function(rangyRange) {
    			try {
    				highlightCssApplier.undoToRange(rangyRange);
    			}
    			catch(e) {
    			}
    		});
    		rangeList = [];
    	}
    
    	function resetSelect() {
    		if(selectRange) {
    			try {
    				selectRange && selectCssApplier.undoToRange(selectRange);
    			}
    			catch(e) {}
    			selectRange = undefined;
    		}
    	}
    
    	var contentElt;
    	var $findReplaceElt, $searchForInputElt, $replaceWithInputElt;
    	var foundCounterElt, $caseSensitiveElt, $regexpElt;
    
    	var previousText = '';
    	var previousCaseSensitive = false;
    	var previousUseRegexp = false;
    	var shown = false;
    	var regex;
    
    	function highlight(force) {
    		if(!shown) {
    			return;
    		}
    		var text = $searchForInputElt.val();
    		var caseSensitive = $caseSensitiveElt.prop('checked');
    		var useRegexp = $regexpElt.prop('checked');
    		if(!force && text == previousText && caseSensitive == previousCaseSensitive && useRegexp == previousUseRegexp) {
    			return;
    		}
    		previousText = text;
    		previousCaseSensitive = caseSensitive;
    		previousUseRegexp = useRegexp;
    
    		resetHighlight();
    		var lastOffset = {};
    		var lastRange;
    
    		function adjustOffset(offset) {
    			if(offset.container === lastOffset.container) {
    				// adjust the offset after rangy has modified the text node
    				return {
    					container: lastRange.endContainer.parentElement.nextSibling,
    					offsetInContainer: offset.offsetInContainer - lastOffset.offsetInContainer,
    					offset: offset.offset
    				};
    			}
    			return offset;
    		}
    
    		offsetList = [];
    		var found = 0;
    		var textLength = text.length;
    		if(textLength) {
    			try {
    				var flags = caseSensitive ? 'gm' : 'gmi';
    				text = useRegexp ? text : text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    				regex = new RegExp(text, flags);
    				editor.getValue().replace(regex, function(match, offset) {
    					offsetList.push({
    						start: offset,
    						end: offset + match.length
    					});
    				});
    				found = offsetList.length;
    				// Highly CPU consuming, so add a limit
    				if(offsetList.length < 200) {
    					var rangeOffsets = [];
    					offsetList.forEach(function(offset) {
    						rangeOffsets.push(offset.start);
    						rangeOffsets.push(offset.end);
    					});
    					rangeOffsets = editor.selectionMgr.findOffsets(rangeOffsets);
    					for(var i = 0; i < rangeOffsets.length; i += 2) {
    						var offsetStart = rangeOffsets[i];
    						var offsetEnd = rangeOffsets[i + 1];
    						var adjustedOffsetStart = adjustOffset(offsetStart);
    						var adjustedOffsetEnd = adjustOffset(offsetEnd);
    						var rangyRange = rangy.createRange();
    						rangyRange.setStart(adjustedOffsetStart.container, adjustedOffsetStart.offsetInContainer);
    						rangyRange.setEnd(adjustedOffsetEnd.container, adjustedOffsetEnd.offsetInContainer);
    						lastOffset = offsetEnd;
    						lastRange = rangyRange;
    						highlightCssApplier.applyToRange(rangyRange);
    						rangeList[offsetStart.offset] = rangyRange;
    					}
    					editor.selectionMgr.hasFocus && editor.selectionMgr.updateSelectionRange();
    				}
    			}
    			catch(e) {
    			}
    		}
    		foundCounterElt.innerHTML = found;
    	}
    
    	function show() {
    		eventMgr.onEditorPopover();
    		shown = true;
    		$findReplaceElt.show();
    		$searchForInputElt.focus()[0].setSelectionRange(0, $searchForInputElt.val().length);
    		editor.selectionMgr.adjustTop = 50;
    		editor.selectionMgr.adjustBottom = 220;
    		highlight(true);
    	}
    
    	function hide() {
    		shown = false;
    		$findReplaceElt.hide();
    		resetHighlight();
    		editor.selectionMgr.adjustTop = 0;
    		editor.selectionMgr.adjustBottom = 0;
    		editor.focus();
    	}
    
    	findReplace.onEditorPopover = function() {
    		hide();
    	};
    
    	function find() {
    		resetSelect();
    		var position = Math.min(editor.selectionMgr.selectionStart, editor.selectionMgr.selectionEnd);
    		var offset = _.find(offsetList, function(offset) {
    			return offset.start > position;
    		});
    		if(!offset) {
    			offset = _.first(offsetList);
    		}
    		if(!offset) {
    			return;
    		}
    		selectRange = rangeList[offset.start];
    		if(!selectRange) {
    			var range = editor.selectionMgr.createRange(offset.start, offset.end);
    			selectRange = rangy.createRange();
    			selectRange.setStart(range.startContainer, range.startOffset);
    			selectRange.setEnd(range.endContainer, range.endOffset);
    		}
    		selectCssApplier.applyToRange(selectRange);
    		selectRange.start = offset.start;
    		selectRange.end = offset.end;
    		editor.selectionMgr.setSelectionStartEnd(offset.start, offset.end);
    		editor.selectionMgr.updateCursorCoordinates(true);
    	}
    
    	function replace() {
    		if(!selectRange) {
    			return find();
    		}
    		var replacement = $replaceWithInputElt.val();
    		editor.replace(selectRange.start, selectRange.end, replacement);
    		setTimeout(function() {
    			find();
    			$replaceWithInputElt.focus();
    		}, 1);
    	}
    
    	function replaceAll() {
    		var replacement = $replaceWithInputElt.val();
    		editor.replaceAll(regex, replacement);
    	}
    
    	findReplace.onContentChanged = _.bind(highlight, null, true);
    	findReplace.onFileOpen = _.bind(highlight, null, true);
    
    	findReplace.onReady = function() {
    		highlightCssApplier = rangy.createCssClassApplier('find-replace-highlight', {
    			normalize: false
    		});
    		selectCssApplier = rangy.createCssClassApplier('find-replace-select', {
    			normalize: false
    		});
    		contentElt = document.querySelector('#wmd-input .editor-content');
    
    		var elt = crel('div', {
    			class: 'find-replace'
    		});
    		$findReplaceElt = $(elt).hide();
    		elt.innerHTML = findReplaceHTML;
    		document.querySelector('.layout-wrapper-l2').appendChild(elt);
    		$('.button-find-replace-dismiss').click(function() {
    			hide();
    		});
    		foundCounterElt = elt.querySelector('.found-counter');
    		$caseSensitiveElt = $findReplaceElt.find('.checkbox-case-sensitive').change(_.bind(highlight, null, false));
    		$regexpElt = $findReplaceElt.find('.checkbox-regexp').change(_.bind(highlight, null, false));
    		$findReplaceElt.find('.search-button').click(find);
    		$searchForInputElt = $('#input-find-replace-search-for').keyup(_.bind(highlight, null, false));
    		$findReplaceElt.find('.replace-button').click(replace);
    		$replaceWithInputElt = $('#input-find-replace-replace-with');
    		$findReplaceElt.find('.replace-all-button').click(replaceAll);
    
    		// Key bindings
    		$().add($searchForInputElt).add($replaceWithInputElt).keydown(function(evt) {
    			if(evt.which === 13) {
    				// Enter key
    				evt.preventDefault();
    				find();
    			}
    		});
    
    		mousetrap.bind(findReplace.config.findReplaceShortcut, function(e) {
    			var newSearch = editor.selectionMgr.getSelectedText();
    			if(newSearch) {
    				$searchForInputElt.val(newSearch);
    			}
    			show();
    			e.preventDefault();
    		});
    	};
    
    	return findReplace;
    });
    
    define('text!html/htmlSanitizerSettingsBlock.html',[],function () { return '<p>Prevents cross-site-scripting attacks (XSS).</p>\n<p class="alert alert-danger"><i class="icon-attention"></i> <b>Careful:</b> Disable at your own risk!</p>\n';});
    
    define('extensions/htmlSanitizer',[
    	// "jquery",
    	"underscore",
    	"utils",
    	"logger",
    	"classes/Extension",
    	"text!html/htmlSanitizerSettingsBlock.html"
    ], function(_, utils, logger, Extension, htmlSanitizerSettingsBlockHTML) {
    
    	var htmlSanitizer = new Extension("htmlSanitizer", "HTML Sanitizer", true);
    	htmlSanitizer.settingsBlock = htmlSanitizerSettingsBlockHTML;
    
    	var buf;
    	htmlSanitizer.onPagedownConfigure = function(editor) {
    		var converter = editor.getConverter();
    		converter.hooks.chain("postConversion", function(html) {
    			buf = [];
    			html.split('<div class="se-preview-section-delimiter"></div>').forEach(function(sectionHtml) {
    				htmlParser(sectionHtml, htmlSanitizeWriter(buf, function(uri, isImage) {
    					return !/^unsafe/.test(sanitizeUri(uri, isImage));
    				}));
    				buf.push('<div class="se-preview-section-delimiter"></div>');
    			});
    			return buf.slice(0, -1).join('');
    		});
    	};
    
    	/**
    	 * @license AngularJS v1.2.16
    	 * (c) 2010-2014 Google, Inc. http://angularjs.org
    	 * License: MIT
    	 */
    
    	var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
    		imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
    
    	function sanitizeUri(uri, isImage) {
    		var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
    		var normalizedVal;
    		normalizedVal = utils.urlResolve(uri).href;
    		if(normalizedVal !== '' && !normalizedVal.match(regex)) {
    			return 'unsafe:' + normalizedVal;
    		}
    	}
    
    	// Regular Expressions for parsing tags and attributes
    	var START_TAG_REGEXP =
    			/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
    		END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
    		ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
    		BEGIN_TAG_REGEXP = /^</,
    		BEGING_END_TAGE_REGEXP = /^<\s*\//,
    		COMMENT_REGEXP = /<!--(.*?)-->/g,
    		DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
    		CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
    		// Match everything outside of normal chars and " (quote character)
    		NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
    
    	function makeMap(str) {
    		var obj = {}, items = str.split(','), i;
    		for(i = 0; i < items.length; i++) {
    			obj[items[i]] = true;
    		}
    		return obj;
    	}
    
    	// Good source of info about elements and attributes
    	// http://dev.w3.org/html5/spec/Overview.html#semantics
    	// http://simon.html5.org/html-elements
    
    	// Safe Void Elements - HTML5
    	// http://dev.w3.org/html5/spec/Overview.html#void-elements
    	var voidElements = makeMap("area,br,col,hr,img,wbr");
    
    	// Elements that you can, intentionally, leave open (and which close themselves)
    	// http://dev.w3.org/html5/spec/Overview.html#optional-tags
    	var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
    		optionalEndTagInlineElements = makeMap("rp,rt"),
    		optionalEndTagElements = _.extend({},
    			optionalEndTagInlineElements,
    			optionalEndTagBlockElements);
    
    	// Safe Block Elements - HTML5
    	var blockElements = _.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
    		"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
    		"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
    
    	// Inline Elements - HTML5
    	var inlineElements = _.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
    		"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
    		"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
    
    
    	// Special Elements (can contain anything)
    	var specialElements = makeMap("script,style");
    
    	// benweet: Add iframe
    	blockElements.iframe = true;
    
    	var validElements = _.extend({},
    		voidElements,
    		blockElements,
    		inlineElements,
    		optionalEndTagElements);
    
    	//Attributes that have href and hence need to be sanitized
    	var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
    	var validAttrs = _.extend({}, uriAttrs, makeMap(
    			'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
    			'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
    			'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
    			'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
    			'valign,value,vspace,width'));
    
    	// benweet: Add id and allowfullscreen (YouTube iframe)
    	validAttrs.id = true;
    	validAttrs.allowfullscreen = true;
    
    	/*
    	 * HTML Parser By Misko Hevery (misko@hevery.com)
    	 * based on:  HTML Parser By John Resig (ejohn.org)
    	 * Original code by Erik Arvidsson, Mozilla Public License
    	 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
    	 *
    	 * // Use like so:
    	 * htmlParser(htmlString, {
    	 *     start: function(tag, attrs, unary) {},
    	 *     end: function(tag) {},
    	 *     chars: function(text) {},
    	 *     comment: function(text) {}
    	 * });
    	 *
    	 */
    	/* jshint -W083 */
    	function htmlParser(html, handler) {
    		var index, chars, match, stack = [], last = html;
    		stack.last = function() {
    			return stack[ stack.length - 1 ];
    		};
    
    		function parseStartTag(tag, tagName, rest, unary) {
    			tagName = tagName && tagName.toLowerCase();
    			if(blockElements[ tagName ]) {
    				while(stack.last() && inlineElements[ stack.last() ]) {
    					parseEndTag("", stack.last());
    				}
    			}
    
    			if(optionalEndTagElements[ tagName ] && stack.last() == tagName) {
    				parseEndTag("", tagName);
    			}
    
    			unary = voidElements[ tagName ] || !!unary;
    
    			if(!unary) {
    				stack.push(tagName);
    			}
    
    			var attrs = {};
    
    			rest.replace(ATTR_REGEXP,
    				function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
    					var value = doubleQuotedValue ||
    						singleQuotedValue ||
    						unquotedValue ||
    						'';
    
    					attrs[name] = decodeEntities(value);
    				});
    			if(handler.start) {
    				handler.start(tagName, attrs, unary);
    			}
    		}
    
    		function parseEndTag(tag, tagName) {
    			var pos = 0, i;
    			tagName = tagName && tagName.toLowerCase();
    			if(tagName) {
    				// Find the closest opened tag of the same type
    				for(pos = stack.length - 1; pos >= 0; pos--) {
    					if(stack[ pos ] == tagName) {
    						break;
    					}
    				}
    			}
    
    			if(pos >= 0) {
    				// Close all the open elements, up the stack
    				for(i = stack.length - 1; i >= pos; i--) {
    					if(handler.end) {
    						handler.end(stack[ i ]);
    					}
    				}
    
    				// Remove the open elements from the stack
    				stack.length = pos;
    			}
    		}
    
    		while(html) {
    			chars = true;
    
    			// Make sure we're not in a script or style element
    			if(!stack.last() || !specialElements[ stack.last() ]) {
    
    				// Comment
    				if(html.indexOf("<!--") === 0) {
    					// comments containing -- are not allowed unless they terminate the comment
    					index = html.indexOf("--", 4);
    
    					if(index >= 0 && html.lastIndexOf("-->", index) === index) {
    						if(handler.comment) {
    							handler.comment(html.substring(4, index));
    						}
    						html = html.substring(index + 3);
    						chars = false;
    					}
    					// DOCTYPE
    				} else if(DOCTYPE_REGEXP.test(html)) {
    					match = html.match(DOCTYPE_REGEXP);
    
    					if(match) {
    						html = html.replace(match[0], '');
    						chars = false;
    					}
    					// end tag
    				} else if(BEGING_END_TAGE_REGEXP.test(html)) {
    					match = html.match(END_TAG_REGEXP);
    
    					if(match) {
    						html = html.substring(match[0].length);
    						match[0].replace(END_TAG_REGEXP, parseEndTag);
    						chars = false;
    					}
    
    					// start tag
    				} else if(BEGIN_TAG_REGEXP.test(html)) {
    					match = html.match(START_TAG_REGEXP);
    
    					if(match) {
    						html = html.substring(match[0].length);
    						match[0].replace(START_TAG_REGEXP, parseStartTag);
    						chars = false;
    					}
    				}
    
    				if(chars) {
    					index = html.indexOf("<");
    
    					var text = index < 0 ? html : html.substring(0, index);
    					html = index < 0 ? "" : html.substring(index);
    
    					if(handler.chars) {
    						handler.chars(decodeEntities(text));
    					}
    				}
    
    			} else {
    				html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
    					function(all, text) {
    						text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
    
    						if(handler.chars) {
    							handler.chars(decodeEntities(text));
    						}
    
    						return "";
    					});
    
    				parseEndTag("", stack.last());
    			}
    
    			if(html == last) {
    				//throw new Error("The sanitizer was unable to parse the following block of html: " + html);
    				stack.reverse();
    				return stack.forEach(function(tag) {
    					buf.push('</');
    					buf.push(tag);
    					buf.push('>');
    				});
    			}
    			last = html;
    		}
    
    		// Clean up any remaining tags
    		parseEndTag();
    	}
    
    	var hiddenPre = document.createElement("pre");
    	var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
    
    	/**
    	 * decodes all entities into regular string
    	 * @param value
    	 * @returns {string} A string with decoded entities.
    	 */
    	function decodeEntities(value) {
    		if(!value) {
    			return '';
    		}
    
    		// Note: IE8 does not preserve spaces at the start/end of innerHTML
    		// so we must capture them and reattach them afterward
    		var parts = spaceRe.exec(value);
    		var spaceBefore = parts[1];
    		var spaceAfter = parts[3];
    		var content = parts[2];
    		if(content) {
    			hiddenPre.innerHTML = content.replace(/</g, "&lt;");
    			// innerText depends on styling as it doesn't display hidden elements.
    			// Therefore, it's better to use textContent not to cause unnecessary
    			// reflows. However, IE<9 don't support textContent so the innerText
    			// fallback is necessary.
    			content = 'textContent' in hiddenPre ?
    				hiddenPre.textContent : hiddenPre.innerText;
    		}
    		return spaceBefore + content + spaceAfter;
    	}
    
    	/**
    	 * Escapes all potentially dangerous characters, so that the
    	 * resulting string can be safely inserted into attribute or
    	 * element text.
    	 * @param value
    	 * @returns {string} escaped text
    	 */
    	function encodeEntities(value) {
    		return value.
    			replace(/&/g, '&amp;').
    			replace(NON_ALPHANUMERIC_REGEXP, function(value) {
    				return '&#' + value.charCodeAt(0) + ';';
    			}).
    			replace(/</g, '&lt;').
    			replace(/>/g, '&gt;');
    	}
    
    
    	/**
    	 * create an HTML/XML writer which writes to buffer
    	 * @param {Array} buf use buf.jain('') to get out sanitized html string
    	 * @returns {object} in the form of {
    	 *     start: function(tag, attrs, unary) {},
    	 *     end: function(tag) {},
    	 *     chars: function(text) {},
    	 *     comment: function(text) {}
    	 * }
    	 */
    	function htmlSanitizeWriter(buf, uriValidator) {
    		var ignore = false;
    		var out = _.bind(buf.push, buf);
    		return {
    			start: function(tag, attrs, unary) {
    				tag = tag && tag.toLowerCase();
    				if(!ignore && specialElements[tag]) {
    					ignore = tag;
    				}
    				if(!ignore && validElements[tag] === true) {
    					out('<');
    					out(tag);
    					_.forEach(attrs, function(value, key) {
    						var lkey = key && key.toLowerCase();
    						var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
    						if(validAttrs[lkey] === true &&
    							(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
    							out(' ');
    							out(key);
    							out('="');
    							out(encodeEntities(value));
    							out('"');
    						}
    					});
    					out(unary ? '/>' : '>');
    				}
    			},
    			end: function(tag) {
    				tag = tag && tag.toLowerCase();
    				if(!ignore && validElements[tag] === true) {
    					out('</');
    					out(tag);
    					out('>');
    				}
    				if(tag == ignore) {
    					ignore = false;
    				}
    			},
    			chars: function(chars) {
    				if(!ignore) {
    					out(encodeEntities(chars));
    				}
    			},
    			comment: function(comment) {
    				if(!ignore) {
    					out('<!--');
    					out(encodeEntities(comment));
    					out('-->');
    				}
    			}
    		};
    	}
    
    	return htmlSanitizer;
    });
    /*
     * waitForImages 1.4.2
     * -------------------
     * Provides a callback when all images have loaded in your given selector.
     * https://github.com/alexanderdickson/waitForImages
     *
     * Copyright (c) 2013 Alex Dickson
     * Licensed under the MIT license.
     */
    (function ($) {
        // Namespace all events.
        var eventNamespace = 'waitForImages';
    
        // CSS properties which contain references to images.
        $.waitForImages = {
            hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage']
        };
    
        // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
        $.expr[':'].uncached = function (obj) {
            // Ensure we are dealing with an `img` element with a valid `src` attribute.
            if (!$(obj).is('img[src!=""]')) {
                return false;
            }
    
            // Firefox's `complete` property will always be `true` even if the image has not been downloaded.
            // Doing it this way works in Firefox.
            var img = new Image();
            img.src = obj.src;
            return !img.complete;
        };
    
        $.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) {
    
            var allImgsLength = 0;
            var allImgsLoaded = 0;
    
            // Handle options object.
            if ($.isPlainObject(arguments[0])) {
                waitForAll = arguments[0].waitForAll;
                eachCallback = arguments[0].each;
    			// This must be last as arguments[0]
    			// is aliased with finishedCallback.
                finishedCallback = arguments[0].finished;
            }
    
            // Handle missing callbacks.
            finishedCallback = finishedCallback || $.noop;
            eachCallback = eachCallback || $.noop;
    
            // Convert waitForAll to Boolean
            waitForAll = !! waitForAll;
    
            // Ensure callbacks are functions.
            if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
                throw new TypeError('An invalid callback was supplied.');
            }
    
            return this.each(function () {
                // Build a list of all imgs, dependent on what images will be considered.
                var obj = $(this);
                var allImgs = [];
                // CSS properties which may contain an image.
                var hasImgProperties = $.waitForImages.hasImageProperties || [];
                // To match `url()` references.
                // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri
                var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g;
    
                if (waitForAll) {
    
                    // Get all elements (including the original), as any one of them could have a background image.
                    obj.find('*').andSelf().each(function () {
                        var element = $(this);
    
                        // If an `img` element, add it. But keep iterating in case it has a background image too.
                        if (element.is('img:uncached')) {
                            allImgs.push({
                                src: element.attr('src'),
                                element: element[0]
                            });
                        }
    
                        $.each(hasImgProperties, function (i, property) {
                            var propertyValue = element.css(property);
                            var match;
    
                            // If it doesn't contain this property, skip.
                            if (!propertyValue) {
                                return true;
                            }
    
                            // Get all url() of this element.
                            while (match = matchUrl.exec(propertyValue)) {
                                allImgs.push({
                                    src: match[2],
                                    element: element[0]
                                });
                            }
                        });
                    });
                } else {
                    // For images only, the task is simpler.
                    obj.find('img:uncached')
                        .each(function () {
                        allImgs.push({
                            src: this.src,
                            element: this
                        });
                    });
                }
    
                allImgsLength = allImgs.length;
                allImgsLoaded = 0;
    
                // If no images found, don't bother.
                if (allImgsLength === 0) {
                    finishedCallback.call(obj[0]);
                }
    
                $.each(allImgs, function (i, img) {
    
                    var image = new Image();
    
                    // Handle the image loading and error with the same callback.
                    $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function (event) {
                        allImgsLoaded++;
    
                        // If an error occurred with loading the image, set the third argument accordingly.
                        eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
    
                        if (allImgsLoaded == allImgsLength) {
                            finishedCallback.call(obj[0]);
                            return false;
                        }
    
                    });
    
                    image.src = img.src;
                });
            });
        };
    }(jQuery));
    
    define("jquery-waitforimages", function(){});
    
    define('eventMgr',[
    	// "jquery",
    	"underscore",
    	"crel",
    	"mousetrap",
    	"utils",
    	"logger",
    	"classes/Extension",
    	"settings",
    	"extensions/yamlFrontMatterParser",
    	"extensions/markdownSectionParser",
    	"extensions/partialRendering",
    	// "extensions/buttonMarkdownSyntax",
    	// "extensions/googleAnalytics",
    	// "extensions/twitter",
    	// "extensions/dialogAbout",
    	// "extensions/dialogManagePublication",
    	// "extensions/dialogManageSynchronization",
    	// "extensions/dialogManageSharing",
    	// "extensions/dialogOpenHarddrive",
    	// "extensions/documentTitle",
    	// "extensions/documentSelector",
    	// "extensions/documentPanel",
    	// "extensions/documentManager",
    	"extensions/workingIndicator",
    	"extensions/notifications",
    	"extensions/umlDiagrams",
    	"extensions/markdownExtra",
    	"extensions/toc",
    	"extensions/mathJax",
    	"extensions/emailConverter",
    	"extensions/scrollSync",
    	// "extensions/buttonSync",
    	// "extensions/buttonPublish",
    	// "extensions/buttonStat",
    	// "extensions/buttonHtmlCode",
    	// "extensions/buttonViewer",
    	// "extensions/welcomeTour",
    	"extensions/shortcuts",
    	// "extensions/userCustom",
    	// "extensions/comments",
    	"extensions/findReplace",
    	"extensions/htmlSanitizer",
    	// "bootstrap",
    	"jquery-waitforimages"
    ], function( _, crel, mousetrap, utils, logger, Extension, settings) {
    
    	var eventMgr = {};
    
    	// Create a list of extensions from module arguments
    	var extensionList = _.chain(arguments).map(function(argument) {
    		return argument instanceof Extension && argument;
    	}).compact().value();
    
    	// Configure extensions
    	var extensionSettings = settings.extensionSettings || {};
    	_.each(extensionList, function(extension) {
    		// Set the extension.config attribute from settings or default
    		// configuration
    		extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
    		if(window.viewerMode === true && extension.disableInViewer === true) {
    			// Skip enabling the extension if we are in the viewer and extension
    			// doesn't support it
    			extension.enabled = false;
    		}
    		else {
    			// Enable the extension if it's not optional or it has not been
    			// disabled by the user
    			extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
    		}
    	});
    
    	// Returns all listeners with the specified name that are implemented in the
    	// enabled extensions
    	function getExtensionListenerList(eventName) {
    		return _.chain(extensionList).map(function(extension) {
    			return extension.enabled && extension[eventName];
    		}).compact().value();
    	}
    
    	// Returns a function that calls every listeners with the specified name
    	// from all enabled extensions
    	var eventListenerListMap = {};
    
    	function createEventHook(eventName) {
    		eventListenerListMap[eventName] = getExtensionListenerList(eventName);
    		return function() {
    			logger.log(eventName, arguments);
    			var eventArguments = arguments;
    			_.each(eventListenerListMap[eventName], function(listener) {
    				// Use try/catch in case userCustom listener contains error
    				try {
    					listener.apply(null, eventArguments);
    				}
    				catch(e) {
    					console.error(_.isObject(e) ? e.stack : e);
    				}
    			});
    		};
    	}
    
    	// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
    	function addEventHook(eventName) {
    		eventMgr[eventName] = createEventHook(eventName);
    	}
    
    	// Used by external modules (not extensions) to listen to events
    	eventMgr.addListener = function(eventName, listener) {
    		try {
    			eventListenerListMap[eventName].push(listener);
    		}
    		catch(e) {
    			console.error('No event listener called ' + eventName);
    		}
    	};
    
    	// Call every onInit listeners (enabled extensions only)
    	createEventHook("onInit")();
    
    	// Load/Save extension config from/to settings
    	eventMgr.onLoadSettings = function() {
    		logger.log("onLoadSettings");
    		_.each(extensionList, function(extension) {
    			var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
    			utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
    			// Special case for Markdown Extra and MathJax
    			if(extension.extensionId == 'markdownExtra') {
    				utils.setInputChecked("#input-settings-markdown-extra", isChecked);
    			}
    			else if(extension.extensionId == 'mathJax') {
    				utils.setInputChecked("#input-settings-mathjax", isChecked);
    			}
    			var onLoadSettingsListener = extension.onLoadSettings;
    			onLoadSettingsListener && onLoadSettingsListener();
    		});
    	};
    	eventMgr.onSaveSettings = function(newExtensionSettings, event) {
    		logger.log("onSaveSettings");
    		_.each(extensionList, function(extension) {
    			var newExtensionConfig = _.extend({}, extension.defaultConfig);
    			newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
    			var isChecked;
    			// Special case for Markdown Extra and MathJax
    			if(extension.extensionId == 'markdownExtra') {
    				isChecked = utils.getInputChecked("#input-settings-markdown-extra");
    				if(isChecked != extension.enabled) {
    					newExtensionConfig.enabled = isChecked;
    				}
    			}
    			else if(extension.extensionId == 'mathJax') {
    				isChecked = utils.getInputChecked("#input-settings-mathjax");
    				if(isChecked != extension.enabled) {
    					newExtensionConfig.enabled = isChecked;
    				}
    			}
    			var onSaveSettingsListener = extension.onSaveSettings;
    			onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
    			newExtensionSettings[extension.extensionId] = newExtensionConfig;
    		});
    	};
    
    	addEventHook("onMessage");
    	addEventHook("onError");
    	addEventHook("onOfflineChanged");
    	addEventHook("onUserActive");
    	addEventHook("onAsyncRunning");
    	addEventHook("onPeriodicRun");
    
    	// To access modules that are loaded after extensions
    	addEventHook("onEditorCreated");
    	addEventHook("onFileMgrCreated");
    	addEventHook("onSynchronizerCreated");
    	addEventHook("onPublisherCreated");
    	addEventHook("onSharingCreated");
    	addEventHook("onEventMgrCreated");
    
    	// Operations on files
    	addEventHook("onFileCreated");
    	addEventHook("onFileDeleted");
    	addEventHook("onFileSelected");
    	addEventHook("onFileOpen");
    	addEventHook("onFileClosed");
    	addEventHook("onContentChanged");
    	addEventHook("onTitleChanged");
    
    	// Operations on folders
    	addEventHook("onFoldersChanged");
    
    	// Sync events
    	addEventHook("onSyncRunning");
    	addEventHook("onSyncSuccess");
    	addEventHook("onSyncImportSuccess");
    	addEventHook("onSyncExportSuccess");
    	addEventHook("onSyncRemoved");
    
    	// Publish events
    	addEventHook("onPublishRunning");
    	addEventHook("onPublishSuccess");
    	addEventHook("onNewPublishSuccess");
    	addEventHook("onPublishRemoved");
    
    	// Operations on Layout
    	addEventHook("onLayoutCreated");
    	addEventHook("onLayoutResize");
    	addEventHook("onExtensionButtonResize");
    
    	// Operations on editor
    	addEventHook("onPagedownConfigure");
    	addEventHook("onSectionsCreated");
    	addEventHook("onCursorCoordinates");
    	addEventHook("onEditorPopover");
    
    	// Operations on comments
    	addEventHook("onDiscussionCreated");
    	addEventHook("onDiscussionRemoved");
    	addEventHook("onCommentsChanged");
    
    	// Refresh twitter buttons
    	addEventHook("onTweet");
    
    
    	var onPreviewFinished = createEventHook("onPreviewFinished");
    	var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
    	var previewContentsElt;
    	var $previewContentsElt;
    	eventMgr.onAsyncPreview = function() {
    		logger.log("onAsyncPreview");
    		function recursiveCall(callbackList) {
    			var callback = callbackList.length ? callbackList.shift() : function() {
    				setTimeout(function() {
    					var html = "";
    					_.each(previewContentsElt.children, function(elt) {
    						html += elt.innerHTML;
    					});
    					var htmlWithComments = utils.trim(html);
    					var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
    					onPreviewFinished(htmlWithComments, htmlWithoutComments);
    				}, 10);
    			};
    			callback(function() {
    				recursiveCall(callbackList);
    			});
    		}
    
    		recursiveCall(onAsyncPreviewListenerList.concat([
    			function(callback) {
    				// We assume some images are loading asynchronously after the preview
    				$previewContentsElt.waitForImages(callback);
    			}
    		]));
    	};
    
    	var onReady = createEventHook("onReady");
    	eventMgr.onReady = function() {
    		previewContentsElt = document.getElementById('preview-contents');
    		$previewContentsElt = $(previewContentsElt);
    
    		// Create a button from an extension listener
    		var createBtn = function(listener) {
    			var buttonGrpElt = crel('div', {
    				class: 'btn-group'
    			});
    			var btnElt = listener();
    			if(_.isString(btnElt)) {
    				buttonGrpElt.innerHTML = btnElt;
    			}
    			else if(_.isElement(btnElt)) {
    				buttonGrpElt.appendChild(btnElt);
    			}
    			return buttonGrpElt;
    		};
    
    		if(window.viewerMode === false) {
    			/*
    			// Create accordion in settings dialog
    			var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
    				return extension.extensionName.toLowerCase();
    			}).reduce(function(html, extension) {
    				return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
    					extensionId: extension.extensionId,
    					extensionName: extension.extensionName,
    					isOptional: extension.isOptional,
    					settingsBlock: extension.settingsBlock
    				}) : "");
    			}, "").value();
    			document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
    			*/
    
    			// Create extension buttons
    			/*
    			logger.log("onCreateButton");
    			var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
    			var extensionButtonsFragment = document.createDocumentFragment();
    			_.each(onCreateButtonListenerList, function(listener) {
    				extensionButtonsFragment.appendChild(createBtn(listener));
    			});
    			document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
    			*/
    		}
    
    		// Create extension preview buttons
    		logger.log("onCreatePreviewButton");
    		var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton");
    		var extensionPreviewButtonsFragment = document.createDocumentFragment();
    		_.each(onCreatePreviewButtonListenerList, function(listener) {
    			extensionPreviewButtonsFragment.appendChild(createBtn(listener));
    		});
    		var previewButtonsElt = document.querySelector('.extension-preview-buttons');
    		if(previewButtonsElt) {
    			previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
    		}
    
    		// Shall close every popover
    		mousetrap.bind('escape', function() {
    			eventMgr.onEditorPopover();
    		});
    
    		// Call onReady listeners
    		onReady();
    	};
    
    	// For extensions that need to call other extensions
    	eventMgr.onEventMgrCreated(eventMgr);
    	return eventMgr;
    });
    
    /**
     * Prism: Lightweight, robust, elegant syntax highlighting
     * MIT license http://www.opensource.org/licenses/mit-license.php/
     * @author Lea Verou http://lea.verou.me
     */
    
    (function(){
    
    // Private helper vars
    var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i;
    
    var _ = self.Prism = {
    	util: {
    		type: function (o) { 
    			return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
    		},
    		
    		// Deep clone a language definition (e.g. to extend it)
    		clone: function (o) {
    			var type = _.util.type(o);
    
    			switch (type) {
    				case 'Object':
    					var clone = {};
    					
    					for (var key in o) {
    						if (o.hasOwnProperty(key)) {
    							clone[key] = _.util.clone(o[key]);
    						}
    					}
    					
    					return clone;
    					
    				case 'Array':
    					return o.slice();
    			}
    			
    			return o;
    		}
    	},
    	
    	languages: {
    		extend: function (id, redef) {
    			var lang = _.util.clone(_.languages[id]);
    			
    			for (var key in redef) {
    				lang[key] = redef[key];
    			}
    			
    			return lang;
    		},
    		
    		// Insert a token before another token in a language literal
    		insertBefore: function (inside, before, insert, root) {
    			root = root || _.languages;
    			var grammar = root[inside];
    			var ret = {};
    				
    			for (var token in grammar) {
    			
    				if (grammar.hasOwnProperty(token)) {
    					
    					if (token == before) {
    					
    						for (var newToken in insert) {
    						
    							if (insert.hasOwnProperty(newToken)) {
    								ret[newToken] = insert[newToken];
    							}
    						}
    					}
    					
    					ret[token] = grammar[token];
    				}
    			}
    			
    			return root[inside] = ret;
    		},
    		
    		// Traverse a language definition with Depth First Search
    		DFS: function(o, callback) {
    			for (var i in o) {
    				callback.call(o, i, o[i]);
    				
    				if (_.util.type(o) === 'Object') {
    					_.languages.DFS(o[i], callback);
    				}
    			}
    		}
    	},
    
    	highlightAll: function(async, callback) {
    		var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');
    
    		for (var i=0, element; element = elements[i++];) {
    			_.highlightElement(element, async === true, callback);
    		}
    	},
    		
    	highlightElement: function(element, async, callback) {
    		// Find language
    		var language, grammar, parent = element;
    		
    		while (parent && !lang.test(parent.className)) {
    			parent = parent.parentNode;
    		}
    		
    		if (parent) {
    			language = (parent.className.match(lang) || [,''])[1];
    			grammar = _.languages[language];
    		}
    
    		if (!grammar) {
    			return;
    		}
    		
    		// Set language on the element, if not present
    		element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
    		
    		// Set language on the parent, for styling
    		parent = element.parentNode;
    		
    		if (/pre/i.test(parent.nodeName)) {
    			parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language; 
    		}
    
    		var code = element.textContent;
    		
    		if(!code) {
    			return;
    		}
    		
    		code = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
    		
    		var env = {
    			element: element,
    			language: language,
    			grammar: grammar,
    			code: code
    		};
    		
    		_.hooks.run('before-highlight', env);
    		
    		if (async && self.Worker) {
    			var worker = new Worker(_.filename);	
    			
    			worker.onmessage = function(evt) {
    				env.highlightedCode = Token.stringify(JSON.parse(evt.data), language);
    
    				_.hooks.run('before-insert', env);
    
    				env.element.innerHTML = env.highlightedCode;
    				
    				callback && callback.call(env.element);
    				_.hooks.run('after-highlight', env);
    			};
    			
    			worker.postMessage(JSON.stringify({
    				language: env.language,
    				code: env.code
    			}));
    		}
    		else {
    			env.highlightedCode = _.highlight(env.code, env.grammar, env.language)
    
    			_.hooks.run('before-insert', env);
    
    			env.element.innerHTML = env.highlightedCode;
    			
    			callback && callback.call(element);
    			
    			_.hooks.run('after-highlight', env);
    		}
    	},
    	
    	highlight: function (text, grammar, language) {
    		return Token.stringify(_.tokenize(text, grammar), language);
    	},
    	
    	tokenize: function(text, grammar, language) {
    		var Token = _.Token;
    		
    		var strarr = [text];
    		
    		var rest = grammar.rest;
    		
    		if (rest) {
    			for (var token in rest) {
    				grammar[token] = rest[token];
    			}
    			
    			delete grammar.rest;
    		}
    								
    		tokenloop: for (var token in grammar) {
    			if(!grammar.hasOwnProperty(token) || !grammar[token]) {
    				continue;
    			}
    			
    			var pattern = grammar[token], 
    				inside = pattern.inside,
    				lookbehind = !!pattern.lookbehind,
    				lookbehindLength = 0;
    			
    			pattern = pattern.pattern || pattern;
    			
    			for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop
    				
    				var str = strarr[i];
    				
    				if (strarr.length > text.length) {
    					// Something went terribly wrong, ABORT, ABORT!
    					break tokenloop;
    				}
    				
    				if (str instanceof Token) {
    					continue;
    				}
    				
    				pattern.lastIndex = 0;
    				
    				var match = pattern.exec(str);
    				
    				if (match) {
    					if(lookbehind) {
    						lookbehindLength = match[1].length;
    					}
    
    					var from = match.index - 1 + lookbehindLength,
    					    match = match[0].slice(lookbehindLength),
    					    len = match.length,
    					    to = from + len,
    						before = str.slice(0, from + 1),
    						after = str.slice(to + 1); 
    
    					var args = [i, 1];
    					
    					if (before) {
    						args.push(before);
    					}
    					
    					var wrapped = new Token(token, inside? _.tokenize(match, inside) : match);
    					
    					args.push(wrapped);
    					
    					if (after) {
    						args.push(after);
    					}
    					
    					Array.prototype.splice.apply(strarr, args);
    				}
    			}
    		}
    
    		return strarr;
    	},
    	
    	hooks: {
    		all: {},
    		
    		add: function (name, callback) {
    			var hooks = _.hooks.all;
    			
    			hooks[name] = hooks[name] || [];
    			
    			hooks[name].push(callback);
    		},
    		
    		run: function (name, env) {
    			var callbacks = _.hooks.all[name];
    			
    			if (!callbacks || !callbacks.length) {
    				return;
    			}
    			
    			for (var i=0, callback; callback = callbacks[i++];) {
    				callback(env);
    			}
    		}
    	}
    };
    
    var Token = _.Token = function(type, content) {
    	this.type = type;
    	this.content = content;
    };
    
    Token.stringify = function(o, language, parent) {
    	if (typeof o == 'string') {
    		return o;
    	}
    
    	if (Object.prototype.toString.call(o) == '[object Array]') {
    		return o.map(function(element) {
    			return Token.stringify(element, language, o);
    		}).join('');
    	}
    	
    	var env = {
    		type: o.type,
    		content: Token.stringify(o.content, language, parent),
    		tag: 'span',
    		classes: ['token', o.type],
    		attributes: {},
    		language: language,
    		parent: parent
    	};
    	
    	if (env.type == 'comment') {
    		env.attributes['spellcheck'] = 'true';
    	}
    	
    	_.hooks.run('wrap', env);
    	
    	var attributes = '';
    	
    	for (var name in env.attributes) {
    		attributes += name + '="' + (env.attributes[name] || '') + '"';
    	}
    	
    	return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>';
    	
    };
    
    if (!self.document) {
    	// In worker
    	self.addEventListener('message', function(evt) {
    		var message = JSON.parse(evt.data),
    		    lang = message.language,
    		    code = message.code;
    		
    		self.postMessage(JSON.stringify(_.tokenize(code, _.languages[lang])));
    		self.close();
    	}, false);
    	
    	return;
    }
    
    // Get current script and highlight
    var script = document.getElementsByTagName('script');
    
    script = script[script.length - 1];
    
    if (script) {
    	_.filename = script.src;
    	
    	if (document.addEventListener && !script.hasAttribute('data-manual')) {
    		document.addEventListener('DOMContentLoaded', _.highlightAll);
    	}
    }
    
    })();
    define("prism-core", (function (global) {
        return function () {
            var ret, fn;
            return ret || global.Prism;
        };
    }(this)));
    
    /**
     * Diff Match and Patch
     *
     * Copyright 2006 Google Inc.
     * http://code.google.com/p/google-diff-match-patch/
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    /**
     * @fileoverview Computes the difference between two texts to create a patch.
     * Applies the patch onto another text, allowing for errors.
     * @author fraser@google.com (Neil Fraser)
     */
    
    /**
     * Class containing the diff, match and patch methods.
     * @constructor
     */
    function diff_match_patch() {
    
      // Defaults.
      // Redefine these in your program to override the defaults.
    
      // Number of seconds to map a diff before giving up (0 for infinity).
      this.Diff_Timeout = 1.0;
      // Cost of an empty edit operation in terms of edit characters.
      this.Diff_EditCost = 4;
      // At what point is no match declared (0.0 = perfection, 1.0 = very loose).
      this.Match_Threshold = 0.5;
      // How far to search for a match (0 = exact location, 1000+ = broad match).
      // A match this many characters away from the expected location will add
      // 1.0 to the score (0.0 is a perfect match).
      this.Match_Distance = 1000;
      // When deleting a large block of text (over ~64 characters), how close do
      // the contents have to be to match the expected contents. (0.0 = perfection,
      // 1.0 = very loose).  Note that Match_Threshold controls how closely the
      // end points of a delete need to match.
      this.Patch_DeleteThreshold = 0.5;
      // Chunk size for context length.
      this.Patch_Margin = 4;
    
      // The number of bits in an int.
      this.Match_MaxBits = 32;
    }
    
    
    //  DIFF FUNCTIONS
    
    
    /**
     * The data structure representing a diff is an array of tuples:
     * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
     * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
     */
    var DIFF_DELETE = -1;
    var DIFF_INSERT = 1;
    var DIFF_EQUAL = 0;
    
    /** @typedef {{0: number, 1: string}} */
    diff_match_patch.Diff;
    
    
    /**
     * Find the differences between two texts.  Simplifies the problem by stripping
     * any common prefix or suffix off the texts before diffing.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {boolean=} opt_checklines Optional speedup flag. If present and false,
     *     then don't run a line-level diff first to identify the changed areas.
     *     Defaults to true, which does a faster, slightly less optimal diff.
     * @param {number} opt_deadline Optional time when the diff should be complete
     *     by.  Used internally for recursive calls.  Users should set DiffTimeout
     *     instead.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     */
    diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines,
        opt_deadline) {
      // Set a deadline by which time the diff must be complete.
      if (typeof opt_deadline == 'undefined') {
        if (this.Diff_Timeout <= 0) {
          opt_deadline = Number.MAX_VALUE;
        } else {
          opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000;
        }
      }
      var deadline = opt_deadline;
    
      // Check for null inputs.
      if (text1 == null || text2 == null) {
        throw new Error('Null input. (diff_main)');
      }
    
      // Check for equality (speedup).
      if (text1 == text2) {
        if (text1) {
          return [[DIFF_EQUAL, text1]];
        }
        return [];
      }
    
      if (typeof opt_checklines == 'undefined') {
        opt_checklines = true;
      }
      var checklines = opt_checklines;
    
      // Trim off common prefix (speedup).
      var commonlength = this.diff_commonPrefix(text1, text2);
      var commonprefix = text1.substring(0, commonlength);
      text1 = text1.substring(commonlength);
      text2 = text2.substring(commonlength);
    
      // Trim off common suffix (speedup).
      commonlength = this.diff_commonSuffix(text1, text2);
      var commonsuffix = text1.substring(text1.length - commonlength);
      text1 = text1.substring(0, text1.length - commonlength);
      text2 = text2.substring(0, text2.length - commonlength);
    
      // Compute the diff on the middle block.
      var diffs = this.diff_compute_(text1, text2, checklines, deadline);
    
      // Restore the prefix and suffix.
      if (commonprefix) {
        diffs.unshift([DIFF_EQUAL, commonprefix]);
      }
      if (commonsuffix) {
        diffs.push([DIFF_EQUAL, commonsuffix]);
      }
      this.diff_cleanupMerge(diffs);
      return diffs;
    };
    
    
    /**
     * Find the differences between two texts.  Assumes that the texts do not
     * have any common prefix or suffix.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {boolean} checklines Speedup flag.  If false, then don't run a
     *     line-level diff first to identify the changed areas.
     *     If true, then run a faster, slightly less optimal diff.
     * @param {number} deadline Time when the diff should be complete by.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines,
        deadline) {
      var diffs;
    
      if (!text1) {
        // Just add some text (speedup).
        return [[DIFF_INSERT, text2]];
      }
    
      if (!text2) {
        // Just delete some text (speedup).
        return [[DIFF_DELETE, text1]];
      }
    
      var longtext = text1.length > text2.length ? text1 : text2;
      var shorttext = text1.length > text2.length ? text2 : text1;
      var i = longtext.indexOf(shorttext);
      if (i != -1) {
        // Shorter text is inside the longer text (speedup).
        diffs = [[DIFF_INSERT, longtext.substring(0, i)],
                 [DIFF_EQUAL, shorttext],
                 [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
        // Swap insertions for deletions if diff is reversed.
        if (text1.length > text2.length) {
          diffs[0][0] = diffs[2][0] = DIFF_DELETE;
        }
        return diffs;
      }
    
      if (shorttext.length == 1) {
        // Single character string.
        // After the previous speedup, the character can't be an equality.
        return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
      }
    
      // Check to see if the problem can be split in two.
      var hm = this.diff_halfMatch_(text1, text2);
      if (hm) {
        // A half-match was found, sort out the return data.
        var text1_a = hm[0];
        var text1_b = hm[1];
        var text2_a = hm[2];
        var text2_b = hm[3];
        var mid_common = hm[4];
        // Send both pairs off for separate processing.
        var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);
        var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);
        // Merge the results.
        return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b);
      }
    
      if (checklines && text1.length > 100 && text2.length > 100) {
        return this.diff_lineMode_(text1, text2, deadline);
      }
    
      return this.diff_bisect_(text1, text2, deadline);
    };
    
    
    /**
     * Do a quick line-level diff on both strings, then rediff the parts for
     * greater accuracy.
     * This speedup can produce non-minimal diffs.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} deadline Time when the diff should be complete by.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) {
      // Scan the text on a line-by-line basis first.
      var a = this.diff_linesToChars_(text1, text2);
      text1 = a.chars1;
      text2 = a.chars2;
      var linearray = a.lineArray;
    
      var diffs = this.diff_main(text1, text2, false, deadline);
    
      // Convert the diff back to original text.
      this.diff_charsToLines_(diffs, linearray);
      // Eliminate freak matches (e.g. blank lines)
      this.diff_cleanupSemantic(diffs);
    
      // Rediff any replacement blocks, this time character-by-character.
      // Add a dummy entry at the end.
      diffs.push([DIFF_EQUAL, '']);
      var pointer = 0;
      var count_delete = 0;
      var count_insert = 0;
      var text_delete = '';
      var text_insert = '';
      while (pointer < diffs.length) {
        switch (diffs[pointer][0]) {
          case DIFF_INSERT:
            count_insert++;
            text_insert += diffs[pointer][1];
            break;
          case DIFF_DELETE:
            count_delete++;
            text_delete += diffs[pointer][1];
            break;
          case DIFF_EQUAL:
            // Upon reaching an equality, check for prior redundancies.
            if (count_delete >= 1 && count_insert >= 1) {
              // Delete the offending records and add the merged ones.
              diffs.splice(pointer - count_delete - count_insert,
                           count_delete + count_insert);
              pointer = pointer - count_delete - count_insert;
              var a = this.diff_main(text_delete, text_insert, false, deadline);
              for (var j = a.length - 1; j >= 0; j--) {
                diffs.splice(pointer, 0, a[j]);
              }
              pointer = pointer + a.length;
            }
            count_insert = 0;
            count_delete = 0;
            text_delete = '';
            text_insert = '';
            break;
        }
        pointer++;
      }
      diffs.pop();  // Remove the dummy entry at the end.
    
      return diffs;
    };
    
    
    /**
     * Find the 'middle snake' of a diff, split the problem in two
     * and return the recursively constructed diff.
     * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} deadline Time at which to bail if not yet complete.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) {
      // Cache the text lengths to prevent multiple calls.
      var text1_length = text1.length;
      var text2_length = text2.length;
      var max_d = Math.ceil((text1_length + text2_length) / 2);
      var v_offset = max_d;
      var v_length = 2 * max_d;
      var v1 = new Array(v_length);
      var v2 = new Array(v_length);
      // Setting all elements to -1 is faster in Chrome & Firefox than mixing
      // integers and undefined.
      for (var x = 0; x < v_length; x++) {
        v1[x] = -1;
        v2[x] = -1;
      }
      v1[v_offset + 1] = 0;
      v2[v_offset + 1] = 0;
      var delta = text1_length - text2_length;
      // If the total number of characters is odd, then the front path will collide
      // with the reverse path.
      var front = (delta % 2 != 0);
      // Offsets for start and end of k loop.
      // Prevents mapping of space beyond the grid.
      var k1start = 0;
      var k1end = 0;
      var k2start = 0;
      var k2end = 0;
      for (var d = 0; d < max_d; d++) {
        // Bail out if deadline is reached.
        if ((new Date()).getTime() > deadline) {
          break;
        }
    
        // Walk the front path one step.
        for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
          var k1_offset = v_offset + k1;
          var x1;
          if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
            x1 = v1[k1_offset + 1];
          } else {
            x1 = v1[k1_offset - 1] + 1;
          }
          var y1 = x1 - k1;
          while (x1 < text1_length && y1 < text2_length &&
                 text1.charAt(x1) == text2.charAt(y1)) {
            x1++;
            y1++;
          }
          v1[k1_offset] = x1;
          if (x1 > text1_length) {
            // Ran off the right of the graph.
            k1end += 2;
          } else if (y1 > text2_length) {
            // Ran off the bottom of the graph.
            k1start += 2;
          } else if (front) {
            var k2_offset = v_offset + delta - k1;
            if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
              // Mirror x2 onto top-left coordinate system.
              var x2 = text1_length - v2[k2_offset];
              if (x1 >= x2) {
                // Overlap detected.
                return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
              }
            }
          }
        }
    
        // Walk the reverse path one step.
        for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
          var k2_offset = v_offset + k2;
          var x2;
          if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
            x2 = v2[k2_offset + 1];
          } else {
            x2 = v2[k2_offset - 1] + 1;
          }
          var y2 = x2 - k2;
          while (x2 < text1_length && y2 < text2_length &&
                 text1.charAt(text1_length - x2 - 1) ==
                 text2.charAt(text2_length - y2 - 1)) {
            x2++;
            y2++;
          }
          v2[k2_offset] = x2;
          if (x2 > text1_length) {
            // Ran off the left of the graph.
            k2end += 2;
          } else if (y2 > text2_length) {
            // Ran off the top of the graph.
            k2start += 2;
          } else if (!front) {
            var k1_offset = v_offset + delta - k2;
            if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
              var x1 = v1[k1_offset];
              var y1 = v_offset + x1 - k1_offset;
              // Mirror x2 onto top-left coordinate system.
              x2 = text1_length - x2;
              if (x1 >= x2) {
                // Overlap detected.
                return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
              }
            }
          }
        }
      }
      // Diff took too long and hit the deadline or
      // number of diffs equals number of characters, no commonality at all.
      return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
    };
    
    
    /**
     * Given the location of the 'middle snake', split the diff in two parts
     * and recurse.
     * @param {string} text1 Old string to be diffed.
     * @param {string} text2 New string to be diffed.
     * @param {number} x Index of split point in text1.
     * @param {number} y Index of split point in text2.
     * @param {number} deadline Time at which to bail if not yet complete.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @private
     */
    diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y,
        deadline) {
      var text1a = text1.substring(0, x);
      var text2a = text2.substring(0, y);
      var text1b = text1.substring(x);
      var text2b = text2.substring(y);
    
      // Compute both diffs serially.
      var diffs = this.diff_main(text1a, text2a, false, deadline);
      var diffsb = this.diff_main(text1b, text2b, false, deadline);
    
      return diffs.concat(diffsb);
    };
    
    
    /**
     * Split two texts into an array of strings.  Reduce the texts to a string of
     * hashes where each Unicode character represents one line.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
     *     An object containing the encoded text1, the encoded text2 and
     *     the array of unique strings.
     *     The zeroth element of the array of unique strings is intentionally blank.
     * @private
     */
    diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) {
      var lineArray = [];  // e.g. lineArray[4] == 'Hello\n'
      var lineHash = {};   // e.g. lineHash['Hello\n'] == 4
    
      // '\x00' is a valid character, but various debuggers don't like it.
      // So we'll insert a junk entry to avoid generating a null character.
      lineArray[0] = '';
    
      /**
       * Split a text into an array of strings.  Reduce the texts to a string of
       * hashes where each Unicode character represents one line.
       * Modifies linearray and linehash through being a closure.
       * @param {string} text String to encode.
       * @return {string} Encoded string.
       * @private
       */
      function diff_linesToCharsMunge_(text) {
        var chars = '';
        // Walk the text, pulling out a substring for each line.
        // text.split('\n') would would temporarily double our memory footprint.
        // Modifying text would create many large strings to garbage collect.
        var lineStart = 0;
        var lineEnd = -1;
        // Keeping our own length variable is faster than looking it up.
        var lineArrayLength = lineArray.length;
        while (lineEnd < text.length - 1) {
          lineEnd = text.indexOf('\n', lineStart);
          if (lineEnd == -1) {
            lineEnd = text.length - 1;
          }
          var line = text.substring(lineStart, lineEnd + 1);
          lineStart = lineEnd + 1;
    
          if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
              (lineHash[line] !== undefined)) {
            chars += String.fromCharCode(lineHash[line]);
          } else {
            chars += String.fromCharCode(lineArrayLength);
            lineHash[line] = lineArrayLength;
            lineArray[lineArrayLength++] = line;
          }
        }
        return chars;
      }
    
      var chars1 = diff_linesToCharsMunge_(text1);
      var chars2 = diff_linesToCharsMunge_(text2);
      return {chars1: chars1, chars2: chars2, lineArray: lineArray};
    };
    
    
    /**
     * Rehydrate the text in a diff from a string of line hashes to real lines of
     * text.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @param {!Array.<string>} lineArray Array of unique strings.
     * @private
     */
    diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) {
      for (var x = 0; x < diffs.length; x++) {
        var chars = diffs[x][1];
        var text = [];
        for (var y = 0; y < chars.length; y++) {
          text[y] = lineArray[chars.charCodeAt(y)];
        }
        diffs[x][1] = text.join('');
      }
    };
    
    
    /**
     * Determine the common prefix of two strings.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the start of each
     *     string.
     */
    diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) {
      // Quick check for common null cases.
      if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
        return 0;
      }
      // Binary search.
      // Performance analysis: http://neil.fraser.name/news/2007/10/09/
      var pointermin = 0;
      var pointermax = Math.min(text1.length, text2.length);
      var pointermid = pointermax;
      var pointerstart = 0;
      while (pointermin < pointermid) {
        if (text1.substring(pointerstart, pointermid) ==
            text2.substring(pointerstart, pointermid)) {
          pointermin = pointermid;
          pointerstart = pointermin;
        } else {
          pointermax = pointermid;
        }
        pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
      }
      return pointermid;
    };
    
    
    /**
     * Determine the common suffix of two strings.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the end of each string.
     */
    diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) {
      // Quick check for common null cases.
      if (!text1 || !text2 ||
          text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {
        return 0;
      }
      // Binary search.
      // Performance analysis: http://neil.fraser.name/news/2007/10/09/
      var pointermin = 0;
      var pointermax = Math.min(text1.length, text2.length);
      var pointermid = pointermax;
      var pointerend = 0;
      while (pointermin < pointermid) {
        if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==
            text2.substring(text2.length - pointermid, text2.length - pointerend)) {
          pointermin = pointermid;
          pointerend = pointermin;
        } else {
          pointermax = pointermid;
        }
        pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
      }
      return pointermid;
    };
    
    
    /**
     * Determine if the suffix of one string is the prefix of another.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {number} The number of characters common to the end of the first
     *     string and the start of the second string.
     * @private
     */
    diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) {
      // Cache the text lengths to prevent multiple calls.
      var text1_length = text1.length;
      var text2_length = text2.length;
      // Eliminate the null case.
      if (text1_length == 0 || text2_length == 0) {
        return 0;
      }
      // Truncate the longer string.
      if (text1_length > text2_length) {
        text1 = text1.substring(text1_length - text2_length);
      } else if (text1_length < text2_length) {
        text2 = text2.substring(0, text1_length);
      }
      var text_length = Math.min(text1_length, text2_length);
      // Quick check for the worst case.
      if (text1 == text2) {
        return text_length;
      }
    
      // Start by looking for a single character match
      // and increase length until no match is found.
      // Performance analysis: http://neil.fraser.name/news/2010/11/04/
      var best = 0;
      var length = 1;
      while (true) {
        var pattern = text1.substring(text_length - length);
        var found = text2.indexOf(pattern);
        if (found == -1) {
          return best;
        }
        length += found;
        if (found == 0 || text1.substring(text_length - length) ==
            text2.substring(0, length)) {
          best = length;
          length++;
        }
      }
    };
    
    
    /**
     * Do the two texts share a substring which is at least half the length of the
     * longer text?
     * This speedup can produce non-minimal diffs.
     * @param {string} text1 First string.
     * @param {string} text2 Second string.
     * @return {Array.<string>} Five element Array, containing the prefix of
     *     text1, the suffix of text1, the prefix of text2, the suffix of
     *     text2 and the common middle.  Or null if there was no match.
     * @private
     */
    diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) {
      if (this.Diff_Timeout <= 0) {
        // Don't risk returning a non-optimal diff if we have unlimited time.
        return null;
      }
      var longtext = text1.length > text2.length ? text1 : text2;
      var shorttext = text1.length > text2.length ? text2 : text1;
      if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
        return null;  // Pointless.
      }
      var dmp = this;  // 'this' becomes 'window' in a closure.
    
      /**
       * Does a substring of shorttext exist within longtext such that the substring
       * is at least half the length of longtext?
       * Closure, but does not reference any external variables.
       * @param {string} longtext Longer string.
       * @param {string} shorttext Shorter string.
       * @param {number} i Start index of quarter length substring within longtext.
       * @return {Array.<string>} Five element Array, containing the prefix of
       *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
       *     of shorttext and the common middle.  Or null if there was no match.
       * @private
       */
      function diff_halfMatchI_(longtext, shorttext, i) {
        // Start with a 1/4 length substring at position i as a seed.
        var seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
        var j = -1;
        var best_common = '';
        var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;
        while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
          var prefixLength = dmp.diff_commonPrefix(longtext.substring(i),
                                                   shorttext.substring(j));
          var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i),
                                                   shorttext.substring(0, j));
          if (best_common.length < suffixLength + prefixLength) {
            best_common = shorttext.substring(j - suffixLength, j) +
                shorttext.substring(j, j + prefixLength);
            best_longtext_a = longtext.substring(0, i - suffixLength);
            best_longtext_b = longtext.substring(i + prefixLength);
            best_shorttext_a = shorttext.substring(0, j - suffixLength);
            best_shorttext_b = shorttext.substring(j + prefixLength);
          }
        }
        if (best_common.length * 2 >= longtext.length) {
          return [best_longtext_a, best_longtext_b,
                  best_shorttext_a, best_shorttext_b, best_common];
        } else {
          return null;
        }
      }
    
      // First check if the second quarter is the seed for a half-match.
      var hm1 = diff_halfMatchI_(longtext, shorttext,
                                 Math.ceil(longtext.length / 4));
      // Check again based on the third quarter.
      var hm2 = diff_halfMatchI_(longtext, shorttext,
                                 Math.ceil(longtext.length / 2));
      var hm;
      if (!hm1 && !hm2) {
        return null;
      } else if (!hm2) {
        hm = hm1;
      } else if (!hm1) {
        hm = hm2;
      } else {
        // Both matched.  Select the longest.
        hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
      }
    
      // A half-match was found, sort out the return data.
      var text1_a, text1_b, text2_a, text2_b;
      if (text1.length > text2.length) {
        text1_a = hm[0];
        text1_b = hm[1];
        text2_a = hm[2];
        text2_b = hm[3];
      } else {
        text2_a = hm[0];
        text2_b = hm[1];
        text1_a = hm[2];
        text1_b = hm[3];
      }
      var mid_common = hm[4];
      return [text1_a, text1_b, text2_a, text2_b, mid_common];
    };
    
    
    /**
     * Reduce the number of edits by eliminating semantically trivial equalities.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
      var changes = false;
      var equalities = [];  // Stack of indices where equalities are found.
      var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
      /** @type {?string} */
      var lastequality = null;
      // Always equal to diffs[equalities[equalitiesLength - 1]][1]
      var pointer = 0;  // Index of current position.
      // Number of characters that changed prior to the equality.
      var length_insertions1 = 0;
      var length_deletions1 = 0;
      // Number of characters that changed after the equality.
      var length_insertions2 = 0;
      var length_deletions2 = 0;
      while (pointer < diffs.length) {
        if (diffs[pointer][0] == DIFF_EQUAL) {  // Equality found.
          equalities[equalitiesLength++] = pointer;
          length_insertions1 = length_insertions2;
          length_deletions1 = length_deletions2;
          length_insertions2 = 0;
          length_deletions2 = 0;
          lastequality = diffs[pointer][1];
        } else {  // An insertion or deletion.
          if (diffs[pointer][0] == DIFF_INSERT) {
            length_insertions2 += diffs[pointer][1].length;
          } else {
            length_deletions2 += diffs[pointer][1].length;
          }
          // Eliminate an equality that is smaller or equal to the edits on both
          // sides of it.
          if (lastequality && (lastequality.length <=
              Math.max(length_insertions1, length_deletions1)) &&
              (lastequality.length <= Math.max(length_insertions2,
                                               length_deletions2))) {
            // Duplicate record.
            diffs.splice(equalities[equalitiesLength - 1], 0,
                         [DIFF_DELETE, lastequality]);
            // Change second copy to insert.
            diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
            // Throw away the equality we just deleted.
            equalitiesLength--;
            // Throw away the previous equality (it needs to be reevaluated).
            equalitiesLength--;
            pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
            length_insertions1 = 0;  // Reset the counters.
            length_deletions1 = 0;
            length_insertions2 = 0;
            length_deletions2 = 0;
            lastequality = null;
            changes = true;
          }
        }
        pointer++;
      }
    
      // Normalize the diff.
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
      this.diff_cleanupSemanticLossless(diffs);
    
      // Find any overlaps between deletions and insertions.
      // e.g: <del>abcxxx</del><ins>xxxdef</ins>
      //   -> <del>abc</del>xxx<ins>def</ins>
      // e.g: <del>xxxabc</del><ins>defxxx</ins>
      //   -> <ins>def</ins>xxx<del>abc</del>
      // Only extract an overlap if it is as big as the edit ahead or behind it.
      pointer = 1;
      while (pointer < diffs.length) {
        if (diffs[pointer - 1][0] == DIFF_DELETE &&
            diffs[pointer][0] == DIFF_INSERT) {
          var deletion = diffs[pointer - 1][1];
          var insertion = diffs[pointer][1];
          var overlap_length1 = this.diff_commonOverlap_(deletion, insertion);
          var overlap_length2 = this.diff_commonOverlap_(insertion, deletion);
          if (overlap_length1 >= overlap_length2) {
            if (overlap_length1 >= deletion.length / 2 ||
                overlap_length1 >= insertion.length / 2) {
              // Overlap found.  Insert an equality and trim the surrounding edits.
              diffs.splice(pointer, 0,
                  [DIFF_EQUAL, insertion.substring(0, overlap_length1)]);
              diffs[pointer - 1][1] =
                  deletion.substring(0, deletion.length - overlap_length1);
              diffs[pointer + 1][1] = insertion.substring(overlap_length1);
              pointer++;
            }
          } else {
            if (overlap_length2 >= deletion.length / 2 ||
                overlap_length2 >= insertion.length / 2) {
              // Reverse overlap found.
              // Insert an equality and swap and trim the surrounding edits.
              diffs.splice(pointer, 0,
                  [DIFF_EQUAL, deletion.substring(0, overlap_length2)]);
              diffs[pointer - 1][0] = DIFF_INSERT;
              diffs[pointer - 1][1] =
                  insertion.substring(0, insertion.length - overlap_length2);
              diffs[pointer + 1][0] = DIFF_DELETE;
              diffs[pointer + 1][1] =
                  deletion.substring(overlap_length2);
              pointer++;
            }
          }
          pointer++;
        }
        pointer++;
      }
    };
    
    
    /**
     * Look for single edits surrounded on both sides by equalities
     * which can be shifted sideways to align the edit to a word boundary.
     * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
      /**
       * Given two strings, compute a score representing whether the internal
       * boundary falls on logical boundaries.
       * Scores range from 6 (best) to 0 (worst).
       * Closure, but does not reference any external variables.
       * @param {string} one First string.
       * @param {string} two Second string.
       * @return {number} The score.
       * @private
       */
      function diff_cleanupSemanticScore_(one, two) {
        if (!one || !two) {
          // Edges are the best.
          return 6;
        }
    
        // Each port of this function behaves slightly differently due to
        // subtle differences in each language's definition of things like
        // 'whitespace'.  Since this function's purpose is largely cosmetic,
        // the choice has been made to use each language's native features
        // rather than force total conformity.
        var char1 = one.charAt(one.length - 1);
        var char2 = two.charAt(0);
        var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_);
        var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_);
        var whitespace1 = nonAlphaNumeric1 &&
            char1.match(diff_match_patch.whitespaceRegex_);
        var whitespace2 = nonAlphaNumeric2 &&
            char2.match(diff_match_patch.whitespaceRegex_);
        var lineBreak1 = whitespace1 &&
            char1.match(diff_match_patch.linebreakRegex_);
        var lineBreak2 = whitespace2 &&
            char2.match(diff_match_patch.linebreakRegex_);
        var blankLine1 = lineBreak1 &&
            one.match(diff_match_patch.blanklineEndRegex_);
        var blankLine2 = lineBreak2 &&
            two.match(diff_match_patch.blanklineStartRegex_);
    
        if (blankLine1 || blankLine2) {
          // Five points for blank lines.
          return 5;
        } else if (lineBreak1 || lineBreak2) {
          // Four points for line breaks.
          return 4;
        } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
          // Three points for end of sentences.
          return 3;
        } else if (whitespace1 || whitespace2) {
          // Two points for whitespace.
          return 2;
        } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
          // One point for non-alphanumeric.
          return 1;
        }
        return 0;
      }
    
      var pointer = 1;
      // Intentionally ignore the first and last element (don't need checking).
      while (pointer < diffs.length - 1) {
        if (diffs[pointer - 1][0] == DIFF_EQUAL &&
            diffs[pointer + 1][0] == DIFF_EQUAL) {
          // This is a single edit surrounded by equalities.
          var equality1 = diffs[pointer - 1][1];
          var edit = diffs[pointer][1];
          var equality2 = diffs[pointer + 1][1];
    
          // First, shift the edit as far left as possible.
          var commonOffset = this.diff_commonSuffix(equality1, edit);
          if (commonOffset) {
            var commonString = edit.substring(edit.length - commonOffset);
            equality1 = equality1.substring(0, equality1.length - commonOffset);
            edit = commonString + edit.substring(0, edit.length - commonOffset);
            equality2 = commonString + equality2;
          }
    
          // Second, step character by character right, looking for the best fit.
          var bestEquality1 = equality1;
          var bestEdit = edit;
          var bestEquality2 = equality2;
          var bestScore = diff_cleanupSemanticScore_(equality1, edit) +
              diff_cleanupSemanticScore_(edit, equality2);
          while (edit.charAt(0) === equality2.charAt(0)) {
            equality1 += edit.charAt(0);
            edit = edit.substring(1) + equality2.charAt(0);
            equality2 = equality2.substring(1);
            var score = diff_cleanupSemanticScore_(equality1, edit) +
                diff_cleanupSemanticScore_(edit, equality2);
            // The >= encourages trailing rather than leading whitespace on edits.
            if (score >= bestScore) {
              bestScore = score;
              bestEquality1 = equality1;
              bestEdit = edit;
              bestEquality2 = equality2;
            }
          }
    
          if (diffs[pointer - 1][1] != bestEquality1) {
            // We have an improvement, save it back to the diff.
            if (bestEquality1) {
              diffs[pointer - 1][1] = bestEquality1;
            } else {
              diffs.splice(pointer - 1, 1);
              pointer--;
            }
            diffs[pointer][1] = bestEdit;
            if (bestEquality2) {
              diffs[pointer + 1][1] = bestEquality2;
            } else {
              diffs.splice(pointer + 1, 1);
              pointer--;
            }
          }
        }
        pointer++;
      }
    };
    
    // Define some regex patterns for matching boundaries.
    diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
    diff_match_patch.whitespaceRegex_ = /\s/;
    diff_match_patch.linebreakRegex_ = /[\r\n]/;
    diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/;
    diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/;
    
    /**
     * Reduce the number of edits by eliminating operationally trivial equalities.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
      var changes = false;
      var equalities = [];  // Stack of indices where equalities are found.
      var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
      /** @type {?string} */
      var lastequality = null;
      // Always equal to diffs[equalities[equalitiesLength - 1]][1]
      var pointer = 0;  // Index of current position.
      // Is there an insertion operation before the last equality.
      var pre_ins = false;
      // Is there a deletion operation before the last equality.
      var pre_del = false;
      // Is there an insertion operation after the last equality.
      var post_ins = false;
      // Is there a deletion operation after the last equality.
      var post_del = false;
      while (pointer < diffs.length) {
        if (diffs[pointer][0] == DIFF_EQUAL) {  // Equality found.
          if (diffs[pointer][1].length < this.Diff_EditCost &&
              (post_ins || post_del)) {
            // Candidate found.
            equalities[equalitiesLength++] = pointer;
            pre_ins = post_ins;
            pre_del = post_del;
            lastequality = diffs[pointer][1];
          } else {
            // Not a candidate, and can never become one.
            equalitiesLength = 0;
            lastequality = null;
          }
          post_ins = post_del = false;
        } else {  // An insertion or deletion.
          if (diffs[pointer][0] == DIFF_DELETE) {
            post_del = true;
          } else {
            post_ins = true;
          }
          /*
           * Five types to be split:
           * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
           * <ins>A</ins>X<ins>C</ins><del>D</del>
           * <ins>A</ins><del>B</del>X<ins>C</ins>
           * <ins>A</del>X<ins>C</ins><del>D</del>
           * <ins>A</ins><del>B</del>X<del>C</del>
           */
          if (lastequality && ((pre_ins && pre_del && post_ins && post_del) ||
                               ((lastequality.length < this.Diff_EditCost / 2) &&
                                (pre_ins + pre_del + post_ins + post_del) == 3))) {
            // Duplicate record.
            diffs.splice(equalities[equalitiesLength - 1], 0,
                         [DIFF_DELETE, lastequality]);
            // Change second copy to insert.
            diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
            equalitiesLength--;  // Throw away the equality we just deleted;
            lastequality = null;
            if (pre_ins && pre_del) {
              // No changes made which could affect previous entry, keep going.
              post_ins = post_del = true;
              equalitiesLength = 0;
            } else {
              equalitiesLength--;  // Throw away the previous equality.
              pointer = equalitiesLength > 0 ?
                  equalities[equalitiesLength - 1] : -1;
              post_ins = post_del = false;
            }
            changes = true;
          }
        }
        pointer++;
      }
    
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
    };
    
    
    /**
     * Reorder and merge like edit sections.  Merge equalities.
     * Any edit section can move as long as it doesn't cross an equality.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     */
    diff_match_patch.prototype.diff_cleanupMerge = function(diffs) {
      diffs.push([DIFF_EQUAL, '']);  // Add a dummy entry at the end.
      var pointer = 0;
      var count_delete = 0;
      var count_insert = 0;
      var text_delete = '';
      var text_insert = '';
      var commonlength;
      while (pointer < diffs.length) {
        switch (diffs[pointer][0]) {
          case DIFF_INSERT:
            count_insert++;
            text_insert += diffs[pointer][1];
            pointer++;
            break;
          case DIFF_DELETE:
            count_delete++;
            text_delete += diffs[pointer][1];
            pointer++;
            break;
          case DIFF_EQUAL:
            // Upon reaching an equality, check for prior redundancies.
            if (count_delete + count_insert > 1) {
              if (count_delete !== 0 && count_insert !== 0) {
                // Factor out any common prefixies.
                commonlength = this.diff_commonPrefix(text_insert, text_delete);
                if (commonlength !== 0) {
                  if ((pointer - count_delete - count_insert) > 0 &&
                      diffs[pointer - count_delete - count_insert - 1][0] ==
                      DIFF_EQUAL) {
                    diffs[pointer - count_delete - count_insert - 1][1] +=
                        text_insert.substring(0, commonlength);
                  } else {
                    diffs.splice(0, 0, [DIFF_EQUAL,
                                        text_insert.substring(0, commonlength)]);
                    pointer++;
                  }
                  text_insert = text_insert.substring(commonlength);
                  text_delete = text_delete.substring(commonlength);
                }
                // Factor out any common suffixies.
                commonlength = this.diff_commonSuffix(text_insert, text_delete);
                if (commonlength !== 0) {
                  diffs[pointer][1] = text_insert.substring(text_insert.length -
                      commonlength) + diffs[pointer][1];
                  text_insert = text_insert.substring(0, text_insert.length -
                      commonlength);
                  text_delete = text_delete.substring(0, text_delete.length -
                      commonlength);
                }
              }
              // Delete the offending records and add the merged ones.
              if (count_delete === 0) {
                diffs.splice(pointer - count_insert,
                    count_delete + count_insert, [DIFF_INSERT, text_insert]);
              } else if (count_insert === 0) {
                diffs.splice(pointer - count_delete,
                    count_delete + count_insert, [DIFF_DELETE, text_delete]);
              } else {
                diffs.splice(pointer - count_delete - count_insert,
                    count_delete + count_insert, [DIFF_DELETE, text_delete],
                    [DIFF_INSERT, text_insert]);
              }
              pointer = pointer - count_delete - count_insert +
                        (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;
            } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {
              // Merge this equality with the previous one.
              diffs[pointer - 1][1] += diffs[pointer][1];
              diffs.splice(pointer, 1);
            } else {
              pointer++;
            }
            count_insert = 0;
            count_delete = 0;
            text_delete = '';
            text_insert = '';
            break;
        }
      }
      if (diffs[diffs.length - 1][1] === '') {
        diffs.pop();  // Remove the dummy entry at the end.
      }
    
      // Second pass: look for single edits surrounded on both sides by equalities
      // which can be shifted sideways to eliminate an equality.
      // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
      var changes = false;
      pointer = 1;
      // Intentionally ignore the first and last element (don't need checking).
      while (pointer < diffs.length - 1) {
        if (diffs[pointer - 1][0] == DIFF_EQUAL &&
            diffs[pointer + 1][0] == DIFF_EQUAL) {
          // This is a single edit surrounded by equalities.
          if (diffs[pointer][1].substring(diffs[pointer][1].length -
              diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) {
            // Shift the edit over the previous equality.
            diffs[pointer][1] = diffs[pointer - 1][1] +
                diffs[pointer][1].substring(0, diffs[pointer][1].length -
                                            diffs[pointer - 1][1].length);
            diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
            diffs.splice(pointer - 1, 1);
            changes = true;
          } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==
              diffs[pointer + 1][1]) {
            // Shift the edit over the next equality.
            diffs[pointer - 1][1] += diffs[pointer + 1][1];
            diffs[pointer][1] =
                diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
                diffs[pointer + 1][1];
            diffs.splice(pointer + 1, 1);
            changes = true;
          }
        }
        pointer++;
      }
      // If shifts were made, the diff needs reordering and another shift sweep.
      if (changes) {
        this.diff_cleanupMerge(diffs);
      }
    };
    
    
    /**
     * loc is a location in text1, compute and return the equivalent location in
     * text2.
     * e.g. 'The cat' vs 'The big cat', 1->1, 5->8
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @param {number} loc Location within text1.
     * @return {number} Location within text2.
     */
    diff_match_patch.prototype.diff_xIndex = function(diffs, loc) {
      var chars1 = 0;
      var chars2 = 0;
      var last_chars1 = 0;
      var last_chars2 = 0;
      var x;
      for (x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_INSERT) {  // Equality or deletion.
          chars1 += diffs[x][1].length;
        }
        if (diffs[x][0] !== DIFF_DELETE) {  // Equality or insertion.
          chars2 += diffs[x][1].length;
        }
        if (chars1 > loc) {  // Overshot the location.
          break;
        }
        last_chars1 = chars1;
        last_chars2 = chars2;
      }
      // Was the location was deleted?
      if (diffs.length != x && diffs[x][0] === DIFF_DELETE) {
        return last_chars2;
      }
      // Add the remaining character length.
      return last_chars2 + (loc - last_chars1);
    };
    
    
    /**
     * Convert a diff array into a pretty HTML report.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} HTML representation.
     */
    diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
      var html = [];
      var pattern_amp = /&/g;
      var pattern_lt = /</g;
      var pattern_gt = />/g;
      var pattern_para = /\n/g;
      for (var x = 0; x < diffs.length; x++) {
        var op = diffs[x][0];    // Operation (insert, delete, equal)
        var data = diffs[x][1];  // Text of change.
        var text = data.replace(pattern_amp, '&amp;').replace(pattern_lt, '&lt;')
            .replace(pattern_gt, '&gt;').replace(pattern_para, '&para;<br>');
        switch (op) {
          case DIFF_INSERT:
            html[x] = '<ins style="background:#e6ffe6;">' + text + '</ins>';
            break;
          case DIFF_DELETE:
            html[x] = '<del style="background:#ffe6e6;">' + text + '</del>';
            break;
          case DIFF_EQUAL:
            html[x] = '<span>' + text + '</span>';
            break;
        }
      }
      return html.join('');
    };
    
    
    /**
     * Compute and return the source text (all equalities and deletions).
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Source text.
     */
    diff_match_patch.prototype.diff_text1 = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_INSERT) {
          text[x] = diffs[x][1];
        }
      }
      return text.join('');
    };
    
    
    /**
     * Compute and return the destination text (all equalities and insertions).
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Destination text.
     */
    diff_match_patch.prototype.diff_text2 = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        if (diffs[x][0] !== DIFF_DELETE) {
          text[x] = diffs[x][1];
        }
      }
      return text.join('');
    };
    
    
    /**
     * Compute the Levenshtein distance; the number of inserted, deleted or
     * substituted characters.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {number} Number of changes.
     */
    diff_match_patch.prototype.diff_levenshtein = function(diffs) {
      var levenshtein = 0;
      var insertions = 0;
      var deletions = 0;
      for (var x = 0; x < diffs.length; x++) {
        var op = diffs[x][0];
        var data = diffs[x][1];
        switch (op) {
          case DIFF_INSERT:
            insertions += data.length;
            break;
          case DIFF_DELETE:
            deletions += data.length;
            break;
          case DIFF_EQUAL:
            // A deletion and an insertion is one substitution.
            levenshtein += Math.max(insertions, deletions);
            insertions = 0;
            deletions = 0;
            break;
        }
      }
      levenshtein += Math.max(insertions, deletions);
      return levenshtein;
    };
    
    
    /**
     * Crush the diff into an encoded string which describes the operations
     * required to transform text1 into text2.
     * E.g. =3\t-2\t+ing  -> Keep 3 chars, delete 2 chars, insert 'ing'.
     * Operations are tab-separated.  Inserted text is escaped using %xx notation.
     * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
     * @return {string} Delta text.
     */
    diff_match_patch.prototype.diff_toDelta = function(diffs) {
      var text = [];
      for (var x = 0; x < diffs.length; x++) {
        switch (diffs[x][0]) {
          case DIFF_INSERT:
            text[x] = '+' + encodeURI(diffs[x][1]);
            break;
          case DIFF_DELETE:
            text[x] = '-' + diffs[x][1].length;
            break;
          case DIFF_EQUAL:
            text[x] = '=' + diffs[x][1].length;
            break;
        }
      }
      return text.join('\t').replace(/%20/g, ' ');
    };
    
    
    /**
     * Given the original text1, and an encoded string which describes the
     * operations required to transform text1 into text2, compute the full diff.
     * @param {string} text1 Source string for the diff.
     * @param {string} delta Delta text.
     * @return {!Array.<!diff_match_patch.Diff>} Array of diff tuples.
     * @throws {!Error} If invalid input.
     */
    diff_match_patch.prototype.diff_fromDelta = function(text1, delta) {
      var diffs = [];
      var diffsLength = 0;  // Keeping our own length var is faster in JS.
      var pointer = 0;  // Cursor in text1
      var tokens = delta.split(/\t/g);
      for (var x = 0; x < tokens.length; x++) {
        // Each token begins with a one character parameter which specifies the
        // operation of this token (delete, insert, equality).
        var param = tokens[x].substring(1);
        switch (tokens[x].charAt(0)) {
          case '+':
            try {
              diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)];
            } catch (ex) {
              // Malformed URI sequence.
              throw new Error('Illegal escape in diff_fromDelta: ' + param);
            }
            break;
          case '-':
            // Fall through.
          case '=':
            var n = parseInt(param, 10);
            if (isNaN(n) || n < 0) {
              throw new Error('Invalid number in diff_fromDelta: ' + param);
            }
            var text = text1.substring(pointer, pointer += n);
            if (tokens[x].charAt(0) == '=') {
              diffs[diffsLength++] = [DIFF_EQUAL, text];
            } else {
              diffs[diffsLength++] = [DIFF_DELETE, text];
            }
            break;
          default:
            // Blank tokens are ok (from a trailing \t).
            // Anything else is an error.
            if (tokens[x]) {
              throw new Error('Invalid diff operation in diff_fromDelta: ' +
                              tokens[x]);
            }
        }
      }
      if (pointer != text1.length) {
        throw new Error('Delta length (' + pointer +
            ') does not equal source text length (' + text1.length + ').');
      }
      return diffs;
    };
    
    
    //  MATCH FUNCTIONS
    
    
    /**
     * Locate the best instance of 'pattern' in 'text' near 'loc'.
     * @param {string} text The text to search.
     * @param {string} pattern The pattern to search for.
     * @param {number} loc The location to search around.
     * @return {number} Best match index or -1.
     */
    diff_match_patch.prototype.match_main = function(text, pattern, loc) {
      // Check for null inputs.
      if (text == null || pattern == null || loc == null) {
        throw new Error('Null input. (match_main)');
      }
    
      loc = Math.max(0, Math.min(loc, text.length));
      if (text == pattern) {
        // Shortcut (potentially not guaranteed by the algorithm)
        return 0;
      } else if (!text.length) {
        // Nothing to match.
        return -1;
      } else if (text.substring(loc, loc + pattern.length) == pattern) {
        // Perfect match at the perfect spot!  (Includes case of null pattern)
        return loc;
      } else {
        // Do a fuzzy compare.
        return this.match_bitap_(text, pattern, loc);
      }
    };
    
    
    /**
     * Locate the best instance of 'pattern' in 'text' near 'loc' using the
     * Bitap algorithm.
     * @param {string} text The text to search.
     * @param {string} pattern The pattern to search for.
     * @param {number} loc The location to search around.
     * @return {number} Best match index or -1.
     * @private
     */
    diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) {
      if (pattern.length > this.Match_MaxBits) {
        throw new Error('Pattern too long for this browser.');
      }
    
      // Initialise the alphabet.
      var s = this.match_alphabet_(pattern);
    
      var dmp = this;  // 'this' becomes 'window' in a closure.
    
      /**
       * Compute and return the score for a match with e errors and x location.
       * Accesses loc and pattern through being a closure.
       * @param {number} e Number of errors in match.
       * @param {number} x Location of match.
       * @return {number} Overall score for match (0.0 = good, 1.0 = bad).
       * @private
       */
      function match_bitapScore_(e, x) {
        var accuracy = e / pattern.length;
        var proximity = Math.abs(loc - x);
        if (!dmp.Match_Distance) {
          // Dodge divide by zero error.
          return proximity ? 1.0 : accuracy;
        }
        return accuracy + (proximity / dmp.Match_Distance);
      }
    
      // Highest score beyond which we give up.
      var score_threshold = this.Match_Threshold;
      // Is there a nearby exact match? (speedup)
      var best_loc = text.indexOf(pattern, loc);
      if (best_loc != -1) {
        score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold);
        // What about in the other direction? (speedup)
        best_loc = text.lastIndexOf(pattern, loc + pattern.length);
        if (best_loc != -1) {
          score_threshold =
              Math.min(match_bitapScore_(0, best_loc), score_threshold);
        }
      }
    
      // Initialise the bit arrays.
      var matchmask = 1 << (pattern.length - 1);
      best_loc = -1;
    
      var bin_min, bin_mid;
      var bin_max = pattern.length + text.length;
      var last_rd;
      for (var d = 0; d < pattern.length; d++) {
        // Scan for the best match; each iteration allows for one more error.
        // Run a binary search to determine how far from 'loc' we can stray at this
        // error level.
        bin_min = 0;
        bin_mid = bin_max;
        while (bin_min < bin_mid) {
          if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) {
            bin_min = bin_mid;
          } else {
            bin_max = bin_mid;
          }
          bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);
        }
        // Use the result from this iteration as the maximum for the next.
        bin_max = bin_mid;
        var start = Math.max(1, loc - bin_mid + 1);
        var finish = Math.min(loc + bin_mid, text.length) + pattern.length;
    
        var rd = Array(finish + 2);
        rd[finish + 1] = (1 << d) - 1;
        for (var j = finish; j >= start; j--) {
          // The alphabet (s) is a sparse hash, so the following line generates
          // warnings.
          var charMatch = s[text.charAt(j - 1)];
          if (d === 0) {  // First pass: exact match.
            rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
          } else {  // Subsequent passes: fuzzy match.
            rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) |
                    (((last_rd[j + 1] | last_rd[j]) << 1) | 1) |
                    last_rd[j + 1];
          }
          if (rd[j] & matchmask) {
            var score = match_bitapScore_(d, j - 1);
            // This match will almost certainly be better than any existing match.
            // But check anyway.
            if (score <= score_threshold) {
              // Told you so.
              score_threshold = score;
              best_loc = j - 1;
              if (best_loc > loc) {
                // When passing loc, don't exceed our current distance from loc.
                start = Math.max(1, 2 * loc - best_loc);
              } else {
                // Already passed loc, downhill from here on in.
                break;
              }
            }
          }
        }
        // No hope for a (better) match at greater error levels.
        if (match_bitapScore_(d + 1, loc) > score_threshold) {
          break;
        }
        last_rd = rd;
      }
      return best_loc;
    };
    
    
    /**
     * Initialise the alphabet for the Bitap algorithm.
     * @param {string} pattern The text to encode.
     * @return {!Object} Hash of character locations.
     * @private
     */
    diff_match_patch.prototype.match_alphabet_ = function(pattern) {
      var s = {};
      for (var i = 0; i < pattern.length; i++) {
        s[pattern.charAt(i)] = 0;
      }
      for (var i = 0; i < pattern.length; i++) {
        s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
      }
      return s;
    };
    
    
    //  PATCH FUNCTIONS
    
    
    /**
     * Increase the context until it is unique,
     * but don't let the pattern expand beyond Match_MaxBits.
     * @param {!diff_match_patch.patch_obj} patch The patch to grow.
     * @param {string} text Source text.
     * @private
     */
    diff_match_patch.prototype.patch_addContext_ = function(patch, text) {
      if (text.length == 0) {
        return;
      }
      var pattern = text.substring(patch.start2, patch.start2 + patch.length1);
      var padding = 0;
    
      // Look for the first and last matches of pattern in text.  If two different
      // matches are found, increase the pattern length.
      while (text.indexOf(pattern) != text.lastIndexOf(pattern) &&
             pattern.length < this.Match_MaxBits - this.Patch_Margin -
             this.Patch_Margin) {
        padding += this.Patch_Margin;
        pattern = text.substring(patch.start2 - padding,
                                 patch.start2 + patch.length1 + padding);
      }
      // Add one chunk for good luck.
      padding += this.Patch_Margin;
    
      // Add the prefix.
      var prefix = text.substring(patch.start2 - padding, patch.start2);
      if (prefix) {
        patch.diffs.unshift([DIFF_EQUAL, prefix]);
      }
      // Add the suffix.
      var suffix = text.substring(patch.start2 + patch.length1,
                                  patch.start2 + patch.length1 + padding);
      if (suffix) {
        patch.diffs.push([DIFF_EQUAL, suffix]);
      }
    
      // Roll back the start points.
      patch.start1 -= prefix.length;
      patch.start2 -= prefix.length;
      // Extend the lengths.
      patch.length1 += prefix.length + suffix.length;
      patch.length2 += prefix.length + suffix.length;
    };
    
    
    /**
     * Compute a list of patches to turn text1 into text2.
     * Use diffs if provided, otherwise compute it ourselves.
     * There are four ways to call this function, depending on what data is
     * available to the caller:
     * Method 1:
     * a = text1, b = text2
     * Method 2:
     * a = diffs
     * Method 3 (optimal):
     * a = text1, b = diffs
     * Method 4 (deprecated, use method 3):
     * a = text1, b = text2, c = diffs
     *
     * @param {string|!Array.<!diff_match_patch.Diff>} a text1 (methods 1,3,4) or
     * Array of diff tuples for text1 to text2 (method 2).
     * @param {string|!Array.<!diff_match_patch.Diff>} opt_b text2 (methods 1,4) or
     * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2).
     * @param {string|!Array.<!diff_match_patch.Diff>} opt_c Array of diff tuples
     * for text1 to text2 (method 4) or undefined (methods 1,2,3).
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     */
    diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) {
      var text1, diffs;
      if (typeof a == 'string' && typeof opt_b == 'string' &&
          typeof opt_c == 'undefined') {
        // Method 1: text1, text2
        // Compute diffs from text1 and text2.
        text1 = /** @type {string} */(a);
        diffs = this.diff_main(text1, /** @type {string} */(opt_b), true);
        if (diffs.length > 2) {
          this.diff_cleanupSemantic(diffs);
          this.diff_cleanupEfficiency(diffs);
        }
      } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' &&
          typeof opt_c == 'undefined') {
        // Method 2: diffs
        // Compute text1 from diffs.
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(a);
        text1 = this.diff_text1(diffs);
      } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' &&
          typeof opt_c == 'undefined') {
        // Method 3: text1, diffs
        text1 = /** @type {string} */(a);
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(opt_b);
      } else if (typeof a == 'string' && typeof opt_b == 'string' &&
          opt_c && typeof opt_c == 'object') {
        // Method 4: text1, text2, diffs
        // text2 is not used.
        text1 = /** @type {string} */(a);
        diffs = /** @type {!Array.<!diff_match_patch.Diff>} */(opt_c);
      } else {
        throw new Error('Unknown call format to patch_make.');
      }
    
      if (diffs.length === 0) {
        return [];  // Get rid of the null case.
      }
      var patches = [];
      var patch = new diff_match_patch.patch_obj();
      var patchDiffLength = 0;  // Keeping our own length var is faster in JS.
      var char_count1 = 0;  // Number of characters into the text1 string.
      var char_count2 = 0;  // Number of characters into the text2 string.
      // Start with text1 (prepatch_text) and apply the diffs until we arrive at
      // text2 (postpatch_text).  We recreate the patches one by one to determine
      // context info.
      var prepatch_text = text1;
      var postpatch_text = text1;
      for (var x = 0; x < diffs.length; x++) {
        var diff_type = diffs[x][0];
        var diff_text = diffs[x][1];
    
        if (!patchDiffLength && diff_type !== DIFF_EQUAL) {
          // A new patch starts here.
          patch.start1 = char_count1;
          patch.start2 = char_count2;
        }
    
        switch (diff_type) {
          case DIFF_INSERT:
            patch.diffs[patchDiffLength++] = diffs[x];
            patch.length2 += diff_text.length;
            postpatch_text = postpatch_text.substring(0, char_count2) + diff_text +
                             postpatch_text.substring(char_count2);
            break;
          case DIFF_DELETE:
            patch.length1 += diff_text.length;
            patch.diffs[patchDiffLength++] = diffs[x];
            postpatch_text = postpatch_text.substring(0, char_count2) +
                             postpatch_text.substring(char_count2 +
                                 diff_text.length);
            break;
          case DIFF_EQUAL:
            if (diff_text.length <= 2 * this.Patch_Margin &&
                patchDiffLength && diffs.length != x + 1) {
              // Small equality inside a patch.
              patch.diffs[patchDiffLength++] = diffs[x];
              patch.length1 += diff_text.length;
              patch.length2 += diff_text.length;
            } else if (diff_text.length >= 2 * this.Patch_Margin) {
              // Time for a new patch.
              if (patchDiffLength) {
                this.patch_addContext_(patch, prepatch_text);
                patches.push(patch);
                patch = new diff_match_patch.patch_obj();
                patchDiffLength = 0;
                // Unlike Unidiff, our patch lists have a rolling context.
                // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
                // Update prepatch text & pos to reflect the application of the
                // just completed patch.
                prepatch_text = postpatch_text;
                char_count1 = char_count2;
              }
            }
            break;
        }
    
        // Update the current character count.
        if (diff_type !== DIFF_INSERT) {
          char_count1 += diff_text.length;
        }
        if (diff_type !== DIFF_DELETE) {
          char_count2 += diff_text.length;
        }
      }
      // Pick up the leftover patch if not empty.
      if (patchDiffLength) {
        this.patch_addContext_(patch, prepatch_text);
        patches.push(patch);
      }
    
      return patches;
    };
    
    
    /**
     * Given an array of patches, return another array that is identical.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     */
    diff_match_patch.prototype.patch_deepCopy = function(patches) {
      // Making deep copies is hard in JavaScript.
      var patchesCopy = [];
      for (var x = 0; x < patches.length; x++) {
        var patch = patches[x];
        var patchCopy = new diff_match_patch.patch_obj();
        patchCopy.diffs = [];
        for (var y = 0; y < patch.diffs.length; y++) {
          patchCopy.diffs[y] = patch.diffs[y].slice();
        }
        patchCopy.start1 = patch.start1;
        patchCopy.start2 = patch.start2;
        patchCopy.length1 = patch.length1;
        patchCopy.length2 = patch.length2;
        patchesCopy[x] = patchCopy;
      }
      return patchesCopy;
    };
    
    
    /**
     * Merge a set of patches onto the text.  Return a patched text, as well
     * as a list of true/false values indicating which patches were applied.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @param {string} text Old text.
     * @return {!Array.<string|!Array.<boolean>>} Two element Array, containing the
     *      new text and an array of boolean values.
     */
    diff_match_patch.prototype.patch_apply = function(patches, text) {
      if (patches.length == 0) {
        return [text, []];
      }
    
      // Deep copy the patches so that no changes are made to originals.
      patches = this.patch_deepCopy(patches);
    
      var nullPadding = this.patch_addPadding(patches);
      text = nullPadding + text + nullPadding;
    
      this.patch_splitMax(patches);
      // delta keeps track of the offset between the expected and actual location
      // of the previous patch.  If there are patches expected at positions 10 and
      // 20, but the first patch was found at 12, delta is 2 and the second patch
      // has an effective expected position of 22.
      var delta = 0;
      var results = [];
      for (var x = 0; x < patches.length; x++) {
        var expected_loc = patches[x].start2 + delta;
        var text1 = this.diff_text1(patches[x].diffs);
        var start_loc;
        var end_loc = -1;
        if (text1.length > this.Match_MaxBits) {
          // patch_splitMax will only provide an oversized pattern in the case of
          // a monster delete.
          start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits),
                                      expected_loc);
          if (start_loc != -1) {
            end_loc = this.match_main(text,
                text1.substring(text1.length - this.Match_MaxBits),
                expected_loc + text1.length - this.Match_MaxBits);
            if (end_loc == -1 || start_loc >= end_loc) {
              // Can't find valid trailing context.  Drop this patch.
              start_loc = -1;
            }
          }
        } else {
          start_loc = this.match_main(text, text1, expected_loc);
        }
        if (start_loc == -1) {
          // No match found.  :(
          results[x] = false;
          // Subtract the delta for this failed patch from subsequent patches.
          delta -= patches[x].length2 - patches[x].length1;
        } else {
          // Found a match.  :)
          results[x] = true;
          delta = start_loc - expected_loc;
          var text2;
          if (end_loc == -1) {
            text2 = text.substring(start_loc, start_loc + text1.length);
          } else {
            text2 = text.substring(start_loc, end_loc + this.Match_MaxBits);
          }
          if (text1 == text2) {
            // Perfect match, just shove the replacement text in.
            text = text.substring(0, start_loc) +
                   this.diff_text2(patches[x].diffs) +
                   text.substring(start_loc + text1.length);
          } else {
            // Imperfect match.  Run a diff to get a framework of equivalent
            // indices.
            var diffs = this.diff_main(text1, text2, false);
            if (text1.length > this.Match_MaxBits &&
                this.diff_levenshtein(diffs) / text1.length >
                this.Patch_DeleteThreshold) {
              // The end points match, but the content is unacceptably bad.
              results[x] = false;
            } else {
              this.diff_cleanupSemanticLossless(diffs);
              var index1 = 0;
              var index2;
              for (var y = 0; y < patches[x].diffs.length; y++) {
                var mod = patches[x].diffs[y];
                if (mod[0] !== DIFF_EQUAL) {
                  index2 = this.diff_xIndex(diffs, index1);
                }
                if (mod[0] === DIFF_INSERT) {  // Insertion
                  text = text.substring(0, start_loc + index2) + mod[1] +
                         text.substring(start_loc + index2);
                } else if (mod[0] === DIFF_DELETE) {  // Deletion
                  text = text.substring(0, start_loc + index2) +
                         text.substring(start_loc + this.diff_xIndex(diffs,
                             index1 + mod[1].length));
                }
                if (mod[0] !== DIFF_DELETE) {
                  index1 += mod[1].length;
                }
              }
            }
          }
        }
      }
      // Strip the padding off.
      text = text.substring(nullPadding.length, text.length - nullPadding.length);
      return [text, results];
    };
    
    
    /**
     * Add some padding on text start and end so that edges can match something.
     * Intended to be called only from within patch_apply.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {string} The padding string added to each side.
     */
    diff_match_patch.prototype.patch_addPadding = function(patches) {
      var paddingLength = this.Patch_Margin;
      var nullPadding = '';
      for (var x = 1; x <= paddingLength; x++) {
        nullPadding += String.fromCharCode(x);
      }
    
      // Bump all the patches forward.
      for (var x = 0; x < patches.length; x++) {
        patches[x].start1 += paddingLength;
        patches[x].start2 += paddingLength;
      }
    
      // Add some padding on start of first diff.
      var patch = patches[0];
      var diffs = patch.diffs;
      if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) {
        // Add nullPadding equality.
        diffs.unshift([DIFF_EQUAL, nullPadding]);
        patch.start1 -= paddingLength;  // Should be 0.
        patch.start2 -= paddingLength;  // Should be 0.
        patch.length1 += paddingLength;
        patch.length2 += paddingLength;
      } else if (paddingLength > diffs[0][1].length) {
        // Grow first equality.
        var extraLength = paddingLength - diffs[0][1].length;
        diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1];
        patch.start1 -= extraLength;
        patch.start2 -= extraLength;
        patch.length1 += extraLength;
        patch.length2 += extraLength;
      }
    
      // Add some padding on end of last diff.
      patch = patches[patches.length - 1];
      diffs = patch.diffs;
      if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) {
        // Add nullPadding equality.
        diffs.push([DIFF_EQUAL, nullPadding]);
        patch.length1 += paddingLength;
        patch.length2 += paddingLength;
      } else if (paddingLength > diffs[diffs.length - 1][1].length) {
        // Grow last equality.
        var extraLength = paddingLength - diffs[diffs.length - 1][1].length;
        diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength);
        patch.length1 += extraLength;
        patch.length2 += extraLength;
      }
    
      return nullPadding;
    };
    
    
    /**
     * Look through the patches and break up any which are longer than the maximum
     * limit of the match algorithm.
     * Intended to be called only from within patch_apply.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     */
    diff_match_patch.prototype.patch_splitMax = function(patches) {
      var patch_size = this.Match_MaxBits;
      for (var x = 0; x < patches.length; x++) {
        if (patches[x].length1 <= patch_size) {
          continue;
        }
        var bigpatch = patches[x];
        // Remove the big old patch.
        patches.splice(x--, 1);
        var start1 = bigpatch.start1;
        var start2 = bigpatch.start2;
        var precontext = '';
        while (bigpatch.diffs.length !== 0) {
          // Create one of several smaller patches.
          var patch = new diff_match_patch.patch_obj();
          var empty = true;
          patch.start1 = start1 - precontext.length;
          patch.start2 = start2 - precontext.length;
          if (precontext !== '') {
            patch.length1 = patch.length2 = precontext.length;
            patch.diffs.push([DIFF_EQUAL, precontext]);
          }
          while (bigpatch.diffs.length !== 0 &&
                 patch.length1 < patch_size - this.Patch_Margin) {
            var diff_type = bigpatch.diffs[0][0];
            var diff_text = bigpatch.diffs[0][1];
            if (diff_type === DIFF_INSERT) {
              // Insertions are harmless.
              patch.length2 += diff_text.length;
              start2 += diff_text.length;
              patch.diffs.push(bigpatch.diffs.shift());
              empty = false;
            } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 &&
                       patch.diffs[0][0] == DIFF_EQUAL &&
                       diff_text.length > 2 * patch_size) {
              // This is a large deletion.  Let it pass in one chunk.
              patch.length1 += diff_text.length;
              start1 += diff_text.length;
              empty = false;
              patch.diffs.push([diff_type, diff_text]);
              bigpatch.diffs.shift();
            } else {
              // Deletion or equality.  Only take as much as we can stomach.
              diff_text = diff_text.substring(0,
                  patch_size - patch.length1 - this.Patch_Margin);
              patch.length1 += diff_text.length;
              start1 += diff_text.length;
              if (diff_type === DIFF_EQUAL) {
                patch.length2 += diff_text.length;
                start2 += diff_text.length;
              } else {
                empty = false;
              }
              patch.diffs.push([diff_type, diff_text]);
              if (diff_text == bigpatch.diffs[0][1]) {
                bigpatch.diffs.shift();
              } else {
                bigpatch.diffs[0][1] =
                    bigpatch.diffs[0][1].substring(diff_text.length);
              }
            }
          }
          // Compute the head context for the next patch.
          precontext = this.diff_text2(patch.diffs);
          precontext =
              precontext.substring(precontext.length - this.Patch_Margin);
          // Append the end context for this patch.
          var postcontext = this.diff_text1(bigpatch.diffs)
                                .substring(0, this.Patch_Margin);
          if (postcontext !== '') {
            patch.length1 += postcontext.length;
            patch.length2 += postcontext.length;
            if (patch.diffs.length !== 0 &&
                patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) {
              patch.diffs[patch.diffs.length - 1][1] += postcontext;
            } else {
              patch.diffs.push([DIFF_EQUAL, postcontext]);
            }
          }
          if (!empty) {
            patches.splice(++x, 0, patch);
          }
        }
      }
    };
    
    
    /**
     * Take a list of patches and return a textual representation.
     * @param {!Array.<!diff_match_patch.patch_obj>} patches Array of Patch objects.
     * @return {string} Text representation of patches.
     */
    diff_match_patch.prototype.patch_toText = function(patches) {
      var text = [];
      for (var x = 0; x < patches.length; x++) {
        text[x] = patches[x];
      }
      return text.join('');
    };
    
    
    /**
     * Parse a textual representation of patches and return a list of Patch objects.
     * @param {string} textline Text representation of patches.
     * @return {!Array.<!diff_match_patch.patch_obj>} Array of Patch objects.
     * @throws {!Error} If invalid input.
     */
    diff_match_patch.prototype.patch_fromText = function(textline) {
      var patches = [];
      if (!textline) {
        return patches;
      }
      var text = textline.split('\n');
      var textPointer = 0;
      var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;
      while (textPointer < text.length) {
        var m = text[textPointer].match(patchHeader);
        if (!m) {
          throw new Error('Invalid patch string: ' + text[textPointer]);
        }
        var patch = new diff_match_patch.patch_obj();
        patches.push(patch);
        patch.start1 = parseInt(m[1], 10);
        if (m[2] === '') {
          patch.start1--;
          patch.length1 = 1;
        } else if (m[2] == '0') {
          patch.length1 = 0;
        } else {
          patch.start1--;
          patch.length1 = parseInt(m[2], 10);
        }
    
        patch.start2 = parseInt(m[3], 10);
        if (m[4] === '') {
          patch.start2--;
          patch.length2 = 1;
        } else if (m[4] == '0') {
          patch.length2 = 0;
        } else {
          patch.start2--;
          patch.length2 = parseInt(m[4], 10);
        }
        textPointer++;
    
        while (textPointer < text.length) {
          var sign = text[textPointer].charAt(0);
          try {
            var line = decodeURI(text[textPointer].substring(1));
          } catch (ex) {
            // Malformed URI sequence.
            throw new Error('Illegal escape in patch_fromText: ' + line);
          }
          if (sign == '-') {
            // Deletion.
            patch.diffs.push([DIFF_DELETE, line]);
          } else if (sign == '+') {
            // Insertion.
            patch.diffs.push([DIFF_INSERT, line]);
          } else if (sign == ' ') {
            // Minor equality.
            patch.diffs.push([DIFF_EQUAL, line]);
          } else if (sign == '@') {
            // Start of next patch.
            break;
          } else if (sign === '') {
            // Blank line?  Whatever.
          } else {
            // WTF?
            throw new Error('Invalid patch mode "' + sign + '" in: ' + line);
          }
          textPointer++;
        }
      }
      return patches;
    };
    
    
    /**
     * Class representing one patch operation.
     * @constructor
     */
    diff_match_patch.patch_obj = function() {
      /** @type {!Array.<!diff_match_patch.Diff>} */
      this.diffs = [];
      /** @type {?number} */
      this.start1 = null;
      /** @type {?number} */
      this.start2 = null;
      /** @type {number} */
      this.length1 = 0;
      /** @type {number} */
      this.length2 = 0;
    };
    
    
    /**
     * Emmulate GNU diff's format.
     * Header: @@ -382,8 +481,9 @@
     * Indicies are printed as 1-based, not 0-based.
     * @return {string} The GNU diff string.
     */
    diff_match_patch.patch_obj.prototype.toString = function() {
      var coords1, coords2;
      if (this.length1 === 0) {
        coords1 = this.start1 + ',0';
      } else if (this.length1 == 1) {
        coords1 = this.start1 + 1;
      } else {
        coords1 = (this.start1 + 1) + ',' + this.length1;
      }
      if (this.length2 === 0) {
        coords2 = this.start2 + ',0';
      } else if (this.length2 == 1) {
        coords2 = this.start2 + 1;
      } else {
        coords2 = (this.start2 + 1) + ',' + this.length2;
      }
      var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n'];
      var op;
      // Escape the body of the patch with %xx notation.
      for (var x = 0; x < this.diffs.length; x++) {
        switch (this.diffs[x][0]) {
          case DIFF_INSERT:
            op = '+';
            break;
          case DIFF_DELETE:
            op = '-';
            break;
          case DIFF_EQUAL:
            op = ' ';
            break;
        }
        text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n';
      }
      return text.join('').replace(/%20/g, ' ');
    };
    
    
    // Export these global variables so that they survive Google's JS compiler.
    // In a browser, 'this' will be 'window'.
    // Users of node.js should 'require' the uncompressed version since Google's
    // JS compiler may break the following exports for non-browser environments.
    this['diff_match_patch'] = diff_match_patch;
    this['DIFF_DELETE'] = DIFF_DELETE;
    this['DIFF_INSERT'] = DIFF_INSERT;
    this['DIFF_EQUAL'] = DIFF_EQUAL;
    
    define("diff_match_patch_uncompressed", (function (global) {
        return function () {
            var ret, fn;
            return ret || global.diff_match_patch;
        };
    }(this)));
    
    !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define('jsondiffpatch',e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.jsondiffpatch=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
    // shim for using process in browser
    
    var process = module.exports = {};
    
    process.nextTick = (function () {
        var canSetImmediate = typeof window !== 'undefined'
        && window.setImmediate;
        var canPost = typeof window !== 'undefined'
        && window.postMessage && window.addEventListener
        ;
    
        if (canSetImmediate) {
            return function (f) { return window.setImmediate(f) };
        }
    
        if (canPost) {
            var queue = [];
            window.addEventListener('message', function (ev) {
                var source = ev.source;
                if ((source === window || source === null) && ev.data === 'process-tick') {
                    ev.stopPropagation();
                    if (queue.length > 0) {
                        var fn = queue.shift();
                        fn();
                    }
                }
            }, true);
    
            return function nextTick(fn) {
                queue.push(fn);
                window.postMessage('process-tick', '*');
            };
        }
    
        return function nextTick(fn) {
            setTimeout(fn, 0);
        };
    })();
    
    process.title = 'browser';
    process.browser = true;
    process.env = {};
    process.argv = [];
    
    process.binding = function (name) {
        throw new Error('process.binding is not supported');
    }
    
    // TODO(shtylman)
    process.cwd = function () { return '/' };
    process.chdir = function (dir) {
        throw new Error('process.chdir is not supported');
    };
    
    },{}],2:[function(_dereq_,module,exports){
    
    var Pipe = _dereq_('../pipe').Pipe;
    
    var Context = function Context(){
    };
    
    Context.prototype.setResult = function(result) {
    	this.result = result;
    	this.hasResult = true;
    	return this;
    };
    
    Context.prototype.exit = function() {
    	this.exiting = true;
    	return this;
    };
    
    Context.prototype.switchTo = function(next, pipe) {
    	if (typeof next === 'string' || next instanceof Pipe) {
    		this.nextPipe = next;
    	} else {
    		this.next = next;
    		if (pipe) {
    			this.nextPipe = pipe;
    		}
    	}
    	return this;
    };
    
    Context.prototype.push = function(child, name) {
    	child.parent = this;
    	if (typeof name !== 'undefined') {
    		child.childName = name;
    	}
    	child.root = this.root || this;
    	child.options = child.options || this.options;
    	if (!this.children) {
    		this.children = [child];
    		this.nextAfterChildren = this.next || null;
    		this.next = child;
    	} else {
    		this.children[this.children.length - 1].next = child;
    		this.children.push(child);
    	}
    	child.next = this;
    	return this;
    };
    
    exports.Context = Context;
    },{"../pipe":15}],3:[function(_dereq_,module,exports){
    
    var Context = _dereq_('./context').Context;
    
    var DiffContext = function DiffContext(left, right){
        this.left = left;
        this.right = right;
        this.pipe = 'diff';
    };
    
    DiffContext.prototype = new Context();
    
    exports.DiffContext = DiffContext;
    },{"./context":2}],4:[function(_dereq_,module,exports){
    
    var Context = _dereq_('./context').Context;
    
    var PatchContext = function PatchContext(left, delta){
        this.left = left;
        this.delta = delta;
        this.pipe = 'patch';
    };
    
    PatchContext.prototype = new Context();
    
    exports.PatchContext = PatchContext;
    },{"./context":2}],5:[function(_dereq_,module,exports){
    
    var Context = _dereq_('./context').Context;
    
    var ReverseContext = function ReverseContext(delta){
        this.delta = delta;
        this.pipe = 'reverse';
    };
    
    ReverseContext.prototype = new Context();
    
    exports.ReverseContext = ReverseContext;
    },{"./context":2}],6:[function(_dereq_,module,exports){
    
    // use as 2nd parameter for JSON.parse to revive Date instances
    module.exports = function dateReviver(key, value) {
        var a;
        if (typeof value === 'string') {
            a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/.exec(value);
            if (a) {
                return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
            }
        }
        return value;
    };
    },{}],7:[function(_dereq_,module,exports){
    
    var Processor = _dereq_('./processor').Processor;
    var Pipe = _dereq_('./pipe').Pipe;
    var DiffContext = _dereq_('./contexts/diff').DiffContext;
    var PatchContext = _dereq_('./contexts/patch').PatchContext;
    var ReverseContext = _dereq_('./contexts/reverse').ReverseContext;
    
    var trivial = _dereq_('./filters/trivial');
    var nested = _dereq_('./filters/nested');
    var arrays = _dereq_('./filters/arrays');
    var dates = _dereq_('./filters/dates');
    var texts = _dereq_('./filters/texts');
    
    var DiffPatcher = function DiffPatcher(options){
        this.processor = new Processor(options);
        this.processor.pipe(new Pipe('diff').append(
            nested.collectChildrenDiffFilter,
            trivial.diffFilter,
            dates.diffFilter,
            texts.diffFilter,
            nested.objectsDiffFilter,
            arrays.diffFilter
            ).shouldHaveResult());
        this.processor.pipe(new Pipe('patch').append(
            nested.collectChildrenPatchFilter,
            arrays.collectChildrenPatchFilter,
            trivial.patchFilter,
            texts.patchFilter,
            nested.patchFilter,
            arrays.patchFilter
            ).shouldHaveResult());
        this.processor.pipe(new Pipe('reverse').append(
            nested.collectChildrenReverseFilter,
            arrays.collectChildrenReverseFilter,
            trivial.reverseFilter,
            texts.reverseFilter,
            nested.reverseFilter,
            arrays.reverseFilter
            ).shouldHaveResult());
    };
    
    DiffPatcher.prototype.options = function() {
        return this.processor.options.apply(this.processor, arguments);
    };
    
    DiffPatcher.prototype.diff = function(left, right) {
        return this.processor.process(new DiffContext(left, right));
    };
    
    DiffPatcher.prototype.patch = function(left, delta) {
        return this.processor.process(new PatchContext(left, delta));
    };
    
    DiffPatcher.prototype.reverse = function(delta) {
        return this.processor.process(new ReverseContext(delta));
    };
    
    DiffPatcher.prototype.unpatch = function(right, delta) {
        return this.patch(right, this.reverse(delta));
    };
    
    exports.DiffPatcher = DiffPatcher;
    
    },{"./contexts/diff":3,"./contexts/patch":4,"./contexts/reverse":5,"./filters/arrays":9,"./filters/dates":10,"./filters/nested":12,"./filters/texts":13,"./filters/trivial":14,"./pipe":15,"./processor":16}],8:[function(_dereq_,module,exports){
    (function (process){
    
    var DiffPatcher = _dereq_('./diffpatcher').DiffPatcher;
    exports.DiffPatcher = DiffPatcher;
    
    exports.create = function(options){
    	return new DiffPatcher(options);
    };
    
    exports.dateReviver = _dereq_('./date-reviver');
    
    var defaultInstance;
    
    exports.diff = function() {
    	if (!defaultInstance) {
    		defaultInstance = new DiffPatcher();
    	}
    	return defaultInstance.diff.apply(defaultInstance, arguments);
    };
    
    exports.patch = function() {
    	if (!defaultInstance) {
    		defaultInstance = new DiffPatcher();
    	}
    	return defaultInstance.patch.apply(defaultInstance, arguments);
    };
    
    exports.unpatch = function() {
    	if (!defaultInstance) {
    		defaultInstance = new DiffPatcher();
    	}
    	return defaultInstance.unpatch.apply(defaultInstance, arguments);
    };
    
    exports.reverse = function() {
    	if (!defaultInstance) {
    		defaultInstance = new DiffPatcher();
    	}
    	return defaultInstance.reverse.apply(defaultInstance, arguments);
    };
    
    var inNode = typeof process !== 'undefined' && typeof process.execPath === 'string';
    if (inNode) {
    	var formatters = _dereq_('./formatters' + '/index');
    	exports.formatters = formatters;
    	// shortcut for console
    	exports.console = formatters.console;
    } else {
    	exports.homepage = 'https://github.com/benjamine/jsondiffpatch';
    	exports.version = '0.1.5';
    }
    
    }).call(this,_dereq_("/home/sheila/proj/JsonDiffPatch/node_modules/gulp-browserify/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js"))
    },{"./date-reviver":6,"./diffpatcher":7,"/home/sheila/proj/JsonDiffPatch/node_modules/gulp-browserify/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":1}],9:[function(_dereq_,module,exports){
    
    var DiffContext = _dereq_('../contexts/diff').DiffContext;
    var PatchContext = _dereq_('../contexts/patch').PatchContext;
    var ReverseContext = _dereq_('../contexts/reverse').ReverseContext;
    
    var lcs = _dereq_('./lcs');
    
    var ARRAY_MOVE = 3;
    
    var isArray = (typeof Array.isArray === 'function') ?
        // use native function
        Array.isArray :
        // use instanceof operator
        function(a) {
            return a instanceof Array;
        };
    
    var arrayIndexOf = typeof Array.prototype.indexOf === 'function' ?
        function(array, item) {
            return array.indexOf(item);
        } : function(array, item) {
            var length = array.length;
            for (var i = 0; i < length; i++) {
                if (array[i] === item) {
                    return i;
                }
            }
            return -1;
        };
    
    var diffFilter = function arraysDiffFilter(context){
        if (!context.leftIsArray) { return; }
    
        var objectHash = context.options && context.options.objectHash;
    
        var match = function(array1, array2, index1, index2, context) {
            var value1 = array1[index1];
            var value2 = array2[index2];
            if (value1 === value2) {
                return true;
            }
            if (typeof value1 !== 'object' || typeof value2 !== 'object') {
                return false;
            }
            if (!objectHash) { return false; }
            var hash1, hash2;
            if (typeof index1 === 'number') {
                context.hashCache1 = context.hashCache1 || [];
                hash1 = context.hashCache1[index1];
                if (typeof hash1 === 'undefined') {
                    context.hashCache1[index1] = hash1 = objectHash(value1, index1);
                }
            } else {
                hash1 = objectHash(value1);
            }
            if (typeof hash1 === 'undefined') {
                return false;
            }
            if (typeof index2 === 'number') {
                context.hashCache2 = context.hashCache2 || [];
                hash2 = context.hashCache2[index2];
                if (typeof hash2 === 'undefined') {
                    context.hashCache2[index2] = hash2 = objectHash(value2, index2);
                }
            } else {
                hash2 = objectHash(value2);
            }
            if (typeof hash2 === 'undefined') {
                return false;
            }
            return hash1 === hash2;
        };
    
        var matchContext = {};
        var commonHead = 0, commonTail = 0, index, index1, index2;
        var array1 = context.left;
        var array2 = context.right;
        var len1 = array1.length;
        var len2 = array2.length;
    
        var child;
    
        // separate common head
        while (commonHead < len1 && commonHead < len2 &&
            match(array1, array2, commonHead, commonHead, matchContext)) {
            index = commonHead;
            child = new DiffContext(context.left[index], context.right[index]);
            context.push(child, index);
            commonHead++;
        }
        // separate common tail
        while (commonTail + commonHead < len1 && commonTail + commonHead < len2 &&
            match(array1, array2, len1 - 1 - commonTail, len2 - 1 - commonTail, matchContext)) {
            index1 = len1 - 1 - commonTail;
            index2 = len2 - 1 - commonTail;
            child = new DiffContext(context.left[index1], context.right[index2]);
            context.push(child, index2);
            commonTail++;
        }
        var result;
        if (commonHead + commonTail === len1) {
            if (len1 === len2) {
                // arrays are identical
                context.setResult(undefined).exit();
                return;
            }
            // trivial case, a block (1 or more consecutive items) was added
            result = result || { _t: 'a' };
            for (index = commonHead; index < len2 - commonTail; index++) {
                result[index] = [array2[index]];
            }
            context.setResult(result).exit();
            return;
        }
        if (commonHead + commonTail === len2) {
            // trivial case, a block (1 or more consecutive items) was removed
            result = result || { _t: 'a' };
            for (index = commonHead; index < len1 - commonTail; index++) {
                result['_'+index] = [array1[index], 0, 0];
            }
            context.setResult(result).exit();
            return;
        }
        // reset hash cache
        matchContext = {};
        // diff is not trivial, find the LCS (Longest Common Subsequence)
        var trimmed1 = array1.slice(commonHead, len1 - commonTail);
        var trimmed2 = array2.slice(commonHead, len2 - commonTail);
        var seq = lcs.get(
            trimmed1, trimmed2,
            match,
            matchContext
        );
        var removedItems = [];
        result = result || { _t: 'a' };
        for (index = commonHead; index < len1 - commonTail; index++) {
            if (arrayIndexOf(seq.indices1, index - commonHead) < 0) {
                // removed
                result['_'+index] = [array1[index], 0, 0];
                removedItems.push(index);
            }
        }
    
        var detectMove = true;
        if (context.options && context.options.arrays && context.options.arrays.detectMove === false) {
            detectMove = false;
        }
        var includeValueOnMove = false;
        if (context.options && context.options.arrays && context.options.arrays.includeValueOnMove) {
            includeValueOnMove = true;
        }
    
        var removedItemsLength = removedItems.length;
        for (index = commonHead; index < len2 - commonTail; index++) {
            var indexOnArray2 = arrayIndexOf(seq.indices2, index - commonHead);
            if (indexOnArray2 < 0) {
                // added, try to match with a removed item and register as position move
                var isMove = false;
                if (detectMove && removedItemsLength > 0) {
                    for (index1 = 0; index1 < removedItemsLength; index1++) {
                        if (match(trimmed1, trimmed2, removedItems[index1] - commonHead,
                            index - commonHead, matchContext)) {
                            // store position move as: [originalValue, newPosition, ARRAY_MOVE]
                            result['_' + removedItems[index1]].splice(1, 2, index, ARRAY_MOVE);
                            if (!includeValueOnMove) {
                                // don't include moved value on diff, to save bytes
                                result['_' + removedItems[index1]][0] = '';
                            }
    
                            index1 = removedItems[index1];
                            index2 = index;
                            child = new DiffContext(context.left[index1], context.right[index2]);
                            context.push(child, index2);
                            removedItems.splice(index1, 1);
                            isMove = true;
                            break;
                        }
                    }
                }
                if (!isMove) {
                    // added
                    result[index] = [array2[index]];
                }
            } else {
                // match, do inner diff
                index1 = seq.indices1[indexOnArray2] + commonHead;
                index2 = seq.indices2[indexOnArray2] + commonHead;
                child = new DiffContext(context.left[index1], context.right[index2]);
                context.push(child, index2);
            }
        }
    
        context.setResult(result).exit();
    
    };
    diffFilter.filterName = 'arrays';
    
    var compare = {
        numerically: function(a, b) {
            return a - b;
        },
        numericallyBy: function(name) {
            return function(a, b) {
                return a[name] - b[name];
            };
        }
    };
    
    var patchFilter = function nestedPatchFilter(context) {
        if (!context.nested) { return; }
        if (context.delta._t !== 'a') { return; }
        var index, index1;
    
        var delta = context.delta;
        var array = context.left;
    
        // first, separate removals, insertions and modifications
        var toRemove = [];
        var toInsert = [];
        var toModify = [];
        for (index in delta) {
            if (index !== '_t') {
                if (index[0] === '_') {
                    // removed item from original array
                    if (delta[index][2] === 0 || delta[index][2] === ARRAY_MOVE) {
                        toRemove.push(parseInt(index.slice(1), 10));
                    } else {
                        throw new Error('only removal or move can be applied at original array indices' +
                            ', invalid diff type: ' + delta[index][2]);
                    }
                } else {
                    if (delta[index].length === 1) {
                        // added item at new array
                        toInsert.push({
                            index: parseInt(index, 10),
                            value: delta[index][0]
                        });
                    } else {
                        // modified item at new array
                        toModify.push({
                            index: parseInt(index, 10),
                            delta: delta[index]
                        });
                    }
                }
            }
        }
    
        // remove items, in reverse order to avoid sawing our own floor
        toRemove = toRemove.sort(compare.numerically);
        for (index = toRemove.length - 1; index >= 0; index--) {
            index1 = toRemove[index];
            var indexDiff = delta['_' + index1];
            var removedValue = array.splice(index1, 1)[0];
            if (indexDiff[2] === ARRAY_MOVE) {
                // reinsert later
                toInsert.push({
                    index: indexDiff[1],
                    value: removedValue
                });
            }
        }
    
        // insert items, in reverse order to avoid moving our own floor
        toInsert = toInsert.sort(compare.numericallyBy('index'));
        var toInsertLength = toInsert.length;
        for (index = 0; index < toInsertLength; index++) {
            var insertion = toInsert[index];
            array.splice(insertion.index, 0, insertion.value);
        }
    
        // apply modifications
        var toModifyLength = toModify.length;
        var child;
        if (toModifyLength > 0) {
            for (index = 0; index < toModifyLength; index++) {
                var modification = toModify[index];
                child = new PatchContext(context.left[modification.index], modification.delta);
                context.push(child, modification.index);
            }
        }
    
        if (!context.children) {
            context.setResult(context.left).exit();
            return;
        }
        context.exit();
    };
    patchFilter.filterName = 'arrays';
    
    var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) {
        if (!context || !context.children) { return; }
        if (context.delta._t !== 'a') { return; }
        var length = context.children.length;
        var child;
        for (var index = 0; index < length; index++) {
            child = context.children[index];
            context.left[child.childName] = child.result;
        }
        context.setResult(context.left).exit();
    };
    collectChildrenPatchFilter.filterName = 'arraysCollectChildren';
    
    var reverseFilter = function arraysReverseFilter(context) {
        if (!context.nested) {
            if (context.delta[2] === ARRAY_MOVE) {
                context.newName = '_' + context.delta[1];
                context.setResult([context.delta[0], parseInt(context.childName.substr(1), 10), ARRAY_MOVE]).exit();
            }
            return;
        }
        if (context.delta._t !== 'a') { return; }
        var name, child;
        for (name in context.delta) {
            if (name === '_t') { continue; }
            child = new ReverseContext(context.delta[name]);
            context.push(child, name);
        }
        context.exit();
    };
    reverseFilter.filterName = 'arrays';
    
    var reverseArrayDeltaIndex = function(delta, index, itemDelta) {
        var newIndex = index;
        if (typeof index === 'string' && index[0] === '_') {
            newIndex = parseInt(index.substr(1), 10);
        } else {
            var uindex = '_' + index;
            if (isArray(itemDelta) && itemDelta[2] === 0) {
                newIndex = uindex;
            } else {
                for (var index2 in delta) {
                    var itemDelta2 = delta[index2];
                    if (isArray(itemDelta2) && itemDelta2[2] === ARRAY_MOVE && itemDelta2[1].toString() === index) {
                        newIndex = index2.substr(1);
                    }
                }
            }
        }
        return newIndex;
    };
    
    var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) {
        if (!context || !context.children) { return; }
        if (context.delta._t !== 'a') { return; }
        var length = context.children.length;
        var child;
        var delta = { _t: 'a' };
        for (var index = 0; index < length; index++) {
            child = context.children[index];
            var name = child.newName;
            if (typeof name === 'undefined') {
                name = reverseArrayDeltaIndex(context.delta, child.childName, child.result);
            }
            if (delta[name] !== child.result) {
                delta[name] = child.result;
            }
        }
        context.setResult(delta).exit();
    };
    collectChildrenReverseFilter.filterName = 'arraysCollectChildren';
    
    exports.diffFilter = diffFilter;
    exports.patchFilter = patchFilter;
    exports.collectChildrenPatchFilter = collectChildrenPatchFilter;
    exports.reverseFilter = reverseFilter;
    exports.collectChildrenReverseFilter = collectChildrenReverseFilter;
    },{"../contexts/diff":3,"../contexts/patch":4,"../contexts/reverse":5,"./lcs":11}],10:[function(_dereq_,module,exports){
    var diffFilter = function datesDiffFilter(context) {
        if (context.left instanceof Date) {
            if (context.right instanceof Date) {
                if (context.left.getTime() !== context.right.getTime()) {
                    context.setResult([context.left, context.right]);
                } else {
                    context.setResult(undefined);
                }
            } else {
                context.setResult([context.left, context.right]);
            }
            context.exit();
        } else if (context.right instanceof Date) {
            context.setResult([context.left, context.right]).exit();
        }
    };
    diffFilter.filterName = 'dates';
    
    exports.diffFilter = diffFilter;
    },{}],11:[function(_dereq_,module,exports){
    /*
    
    LCS implementation that supports arrays or strings
    
    reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
    
    */
    
    var defaultMatch = function(array1, array2, index1, index2) {
        return array1[index1] === array2[index2];
    };
    
    var lengthMatrix = function(array1, array2, match, context) {
        var len1 = array1.length;
        var len2 = array2.length;
        var x, y;
    
        // initialize empty matrix of len1+1 x len2+1
        var matrix = [len1 + 1];
        for (x = 0; x < len1 + 1; x++) {
            matrix[x] = [len2 + 1];
            for (y = 0; y < len2 + 1; y++) {
                matrix[x][y] = 0;
            }
        }
        matrix.match = match;
        // save sequence lengths for each coordinate
        for (x = 1; x < len1 + 1; x++) {
            for (y = 1; y < len2 + 1; y++) {
                if (match(array1, array2, x - 1, y - 1, context)) {
                    matrix[x][y] = matrix[x - 1][y - 1] + 1;
                } else {
                    matrix[x][y] = Math.max(matrix[x - 1][y], matrix[x][y - 1]);
                }
            }
        }
        return matrix;
    };
    
    var backtrack = function(matrix, array1, array2, index1, index2, context) {
        if (index1 === 0 || index2 === 0) {
            return {
                sequence: [],
                indices1: [],
                indices2: []
            };
        }
    
        if (matrix.match(array1, array2, index1 - 1, index2 - 1, context)) {
            var subsequence = backtrack(matrix, array1, array2, index1 - 1, index2 - 1, context);
            subsequence.sequence.push(array1[index1 - 1]);
            subsequence.indices1.push(index1 - 1);
            subsequence.indices2.push(index2 - 1);
            return subsequence;
        }
    
        if (matrix[index1][index2 - 1] > matrix[index1 - 1][index2]) {
            return backtrack(matrix, array1, array2, index1, index2 - 1, context);
        } else {
            return backtrack(matrix, array1, array2, index1 - 1, index2, context);
        }
    };
    
    var get = function(array1, array2, match, context) {
        context = context || {};
        var matrix = lengthMatrix(array1, array2, match || defaultMatch, context);
        var result = backtrack(matrix, array1, array2, array1.length, array2.length, context);
        if (typeof array1 === 'string' && typeof array2 === 'string') {
            result.sequence = result.sequence.join('');
        }
        return result;
    };
    
    exports.get = get;
    
    },{}],12:[function(_dereq_,module,exports){
    
    var DiffContext = _dereq_('../contexts/diff').DiffContext;
    var PatchContext = _dereq_('../contexts/patch').PatchContext;
    var ReverseContext = _dereq_('../contexts/reverse').ReverseContext;
    
    var collectChildrenDiffFilter = function collectChildrenDiffFilter(context) {
        if (!context || !context.children) { return; }
        var length = context.children.length;
        var child;
        var result = context.result;
        for (var index = 0; index < length; index++) {
            child = context.children[index];
            if (typeof child.result === 'undefined') {
                continue;
            }
            result = result || {};
            result[child.childName] = child.result;
        }
        if (result && context.leftIsArray) {
            result._t = 'a';
        }
        context.setResult(result).exit();
    };
    collectChildrenDiffFilter.filterName = 'collectChildren';
    
    var objectsDiffFilter = function objectsDiffFilter(context) {
        if (context.leftIsArray || context.leftType !== 'object') { return; }
    
        var name, child;
        for (name in context.left) {
            child = new DiffContext(context.left[name], context.right[name]);
            context.push(child, name);
        }
        for (name in context.right) {
            if (typeof context.left[name] === 'undefined') {
                child = new DiffContext(undefined, context.right[name]);
                context.push(child, name);
            }
        }
    
        if (!context.children || context.children.length === 0) {
            context.setResult(undefined).exit();
            return;
        }
        context.exit();
    };
    objectsDiffFilter.filterName = 'objects';
    
    var patchFilter = function nestedPatchFilter(context) {
        if (!context.nested) { return; }
        if (context.delta._t) { return; }
        var name, child;
        for (name in context.delta) {
            child = new PatchContext(context.left[name], context.delta[name]);
            context.push(child, name);
        }
        context.exit();
    };
    patchFilter.filterName = 'objects';
    
    var collectChildrenPatchFilter = function collectChildrenPatchFilter(context) {
        if (!context || !context.children) { return; }
        if (context.delta._t) { return; }
        var length = context.children.length;
        var child;
        for (var index = 0; index < length; index++) {
            child = context.children[index];
            if (context.left[child.childName] !== child.result) {
                context.left[child.childName] = child.result;
            }
        }
        context.setResult(context.left).exit();
    };
    collectChildrenPatchFilter.filterName = 'collectChildren';
    
    var reverseFilter = function nestedReverseFilter(context) {
        if (!context.nested) { return; }
        if (context.delta._t) { return; }
        var name, child;
        for (name in context.delta) {
            child = new ReverseContext(context.delta[name]);
            context.push(child, name);
        }
        context.exit();
    };
    reverseFilter.filterName = 'objects';
    
    var collectChildrenReverseFilter = function collectChildrenReverseFilter(context) {
        if (!context || !context.children) { return; }
        if (context.delta._t) { return; }
        var length = context.children.length;
        var child;
        var delta = {};
        for (var index = 0; index < length; index++) {
            child = context.children[index];
            if (delta[child.childName] !== child.result) {
                delta[child.childName] = child.result;
            }
        }
        context.setResult(delta).exit();
    };
    collectChildrenReverseFilter.filterName = 'collectChildren';
    
    exports.collectChildrenDiffFilter = collectChildrenDiffFilter;
    exports.objectsDiffFilter = objectsDiffFilter;
    exports.patchFilter = patchFilter;
    exports.collectChildrenPatchFilter = collectChildrenPatchFilter;
    exports.reverseFilter = reverseFilter;
    exports.collectChildrenReverseFilter = collectChildrenReverseFilter;
    },{"../contexts/diff":3,"../contexts/patch":4,"../contexts/reverse":5}],13:[function(_dereq_,module,exports){
    /* global diff_match_patch */
    var TEXT_DIFF = 2;
    var DEFAULT_MIN_LENGTH = 60;
    var cachedDiffPatch = null;
    
    var getDiffMatchPatch = function(){
        /*jshint camelcase: false */
    
        if (!cachedDiffPatch) {
            var instance;
            if (typeof diff_match_patch !== 'undefined') {
                // already loaded, probably a browser
                instance = new diff_match_patch();
            } else if (typeof _dereq_ === 'function') {
                var dmp = _dereq_('../../external/diff_match_patch_uncompressed');
                instance = new dmp.diff_match_patch();
            }
            if (!instance) {
                var error = new Error('text diff_match_patch library not found');
                error.diff_match_patch_not_found = true;
                throw error;
            }
            cachedDiffPatch = {
                diff: function(txt1, txt2){
                    return instance.patch_toText(instance.patch_make(txt1, txt2));
                },
                patch: function(txt1, patch){
                    var results = instance.patch_apply(instance.patch_fromText(patch), txt1);
                    for (var i = 0; i < results[1].length; i++) {
                        if (!results[1][i]) {
                            var error = new Error('text patch failed');
                            error.textPatchFailed = true;
                        }
                    }
                    return results[0];
                }
            };
        }
        return cachedDiffPatch;
    };
    
    var diffFilter = function textsDiffFilter(context) {
        if (context.leftType !== 'string') { return; }
        var minLength = (context.options && context.options.textDiff &&
            context.options.textDiff.minLength) || DEFAULT_MIN_LENGTH;
        if (context.left.length < minLength ||
            context.right.length < minLength) {
            context.setResult([context.left, context.right]).exit();
            return;
        }
        // large text, use a text-diff algorithm
        var diff = getDiffMatchPatch().diff;
        context.setResult([diff(context.left, context.right), 0, TEXT_DIFF]).exit();
    };
    diffFilter.filterName = 'texts';
    
    var patchFilter = function textsPatchFilter(context) {
        if (context.nested) { return; }
        if (context.delta[2] !== TEXT_DIFF) { return; }
    
        // text-diff, use a text-patch algorithm
        var patch = getDiffMatchPatch().patch;
        context.setResult(patch(context.left, context.delta[0])).exit();
    };
    patchFilter.filterName = 'texts';
    
    var textDeltaReverse = function(delta){
        var i, l, lines, line, lineTmp, header = null,
        headerRegex = /^@@ +\-(\d+),(\d+) +\+(\d+),(\d+) +@@$/,
        lineHeader, lineAdd, lineRemove;
        lines = delta.split('\n');
        for (i = 0, l = lines.length; i<l; i++) {
            line = lines[i];
            var lineStart = line.slice(0,1);
            if (lineStart==='@'){
                header = headerRegex.exec(line);
                lineHeader = i;
                lineAdd = null;
                lineRemove = null;
    
                // fix header
                lines[lineHeader] = '@@ -' + header[3] + ',' + header[4] + ' +' + header[1] + ',' + header[2] + ' @@';
            } else if (lineStart === '+'){
                lineAdd = i;
                lines[i] = '-' + lines[i].slice(1);
                if (lines[i-1].slice(0,1)==='+') {
                    // swap lines to keep default order (-+)
                    lineTmp = lines[i];
                    lines[i] = lines[i-1];
                    lines[i-1] = lineTmp;
                }
            } else if (lineStart === '-'){
                lineRemove = i;
                lines[i] = '+' + lines[i].slice(1);
            }
        }
        return lines.join('\n');
    };
    
    var reverseFilter = function textsReverseFilter(context) {
        if (context.nested) { return; }
        if (context.delta[2] !== TEXT_DIFF) { return; }
    
        // text-diff, use a text-diff algorithm
        context.setResult([textDeltaReverse(context.delta[0]), 0, TEXT_DIFF]).exit();
    };
    reverseFilter.filterName = 'texts';
    
    exports.diffFilter = diffFilter;
    exports.patchFilter = patchFilter;
    exports.reverseFilter = reverseFilter;
    },{}],14:[function(_dereq_,module,exports){
    
    var isArray = (typeof Array.isArray === 'function') ?
        // use native function
        Array.isArray :
        // use instanceof operator
        function(a) {
            return a instanceof Array;
        };
    
    var diffFilter = function trivialMatchesDiffFilter(context) {
        if (context.left === context.right) {
            context.setResult(undefined).exit();
            return;
        }
        if (typeof context.left === 'undefined') {
            if (typeof context.right === 'function') {
                throw new Error('functions are not supported');
            }
            context.setResult([context.right]).exit();
            return;
        }
        if (typeof context.right === 'undefined') {
            context.setResult([context.left, 0, 0]).exit();
            return;
        }
        if (typeof context.left === 'function' || typeof context.right === 'function' ) {
            throw new Error('functions are not supported');
        }
        context.leftType = context.left === null ? 'null' : typeof context.left;
        context.rightType = context.right === null ? 'null' : typeof context.right;
        if (context.leftType !== context.rightType) {
            context.setResult([context.left, context.right]).exit();
            return;
        }
        if (context.leftType === 'boolean' || context.leftType === 'number') {
            context.setResult([context.left, context.right]).exit();
            return;
        }
        if (context.leftType === 'object') {
            context.leftIsArray = isArray(context.left);
        }
        if (context.rightType === 'object') {
            context.rightIsArray = isArray(context.right);
        }
        if (context.leftIsArray !== context.rightIsArray) {
            context.setResult([context.left, context.right]).exit();
            return;
        }
    };
    diffFilter.filterName = 'trivial';
    
    var patchFilter = function trivialMatchesPatchFilter(context) {
        if (typeof context.delta === 'undefined') {
            context.setResult(context.left).exit();
            return;
        }
        context.nested = !isArray(context.delta);
        if (context.nested) { return; }
        if (context.delta.length === 1) {
            context.setResult(context.delta[0]).exit();
            return;
        }
        if (context.delta.length === 2) {
            context.setResult(context.delta[1]).exit();
            return;
        }
        if (context.delta.length === 3 && context.delta[2] === 0) {
            context.setResult(undefined).exit();
            return;
        }
    };
    patchFilter.filterName = 'trivial';
    
    var reverseFilter = function trivialReferseFilter(context) {
        if (typeof context.delta === 'undefined') {
            context.setResult(context.delta).exit();
            return;
        }
        context.nested = !isArray(context.delta);
        if (context.nested) { return; }
        if (context.delta.length === 1) {
            context.setResult([context.delta[0], 0, 0]).exit();
            return;
        }
        if (context.delta.length === 2) {
            context.setResult([context.delta[1], context.delta[0]]).exit();
            return;
        }
        if (context.delta.length === 3 && context.delta[2] === 0) {
            context.setResult([context.delta[0]]).exit();
            return;
        }
    };
    reverseFilter.filterName = 'trivial';
    
    exports.diffFilter = diffFilter;
    exports.patchFilter = patchFilter;
    exports.reverseFilter = reverseFilter;
    
    },{}],15:[function(_dereq_,module,exports){
    
    var Pipe = function Pipe(name){
        this.name = name;
        this.filters = [];
    };
    
    Pipe.prototype.process = function(input) {
        if (!this.processor) {
            throw new Error('add this pipe to a processor before using it');
        }
        var debug = this.debug;
        var length = this.filters.length;
        var context = input;
        for (var index = 0; index < length; index++) {
            var filter = this.filters[index];
            if (debug) {
                this.log('filter: ' + filter.filterName);
            }
            filter(context);
            if (typeof context === 'object' && context.exiting) {
                context.exiting = false;
                break;
            }
        }
        if (!context.next && this.resultCheck) {
            this.resultCheck(context);
        }
    };
    
    Pipe.prototype.log = function(msg) {
        console.log('[jsondiffpatch] ' + this.name + ' pipe, ' + msg);
    };
    
    Pipe.prototype.append = function() {
        this.filters.push.apply(this.filters, arguments);
        return this;
    };
    
    Pipe.prototype.prepend = function() {
        this.filters.unshift.apply(this.filters, arguments);
        return this;
    };
    
    Pipe.prototype.indexOf = function(filterName) {
        if (!filterName) {
            throw new Error('a filter name is required');
        }
        for (var index = 0; index < this.filters.length; index++) {
            var filter = this.filters[index];
            if (filter.filterName === filterName) {
                return index;
            }
        }
        throw new Error('filter not found: ' + filterName);
    };
    
    Pipe.prototype.list = function() {
        var names = [];
        for (var index = 0; index < this.filters.length; index++) {
            var filter = this.filters[index];
            names.push(filter.filterName);
        }
        return names;
    };
    
    Pipe.prototype.after = function(filterName) {
        var index = this.indexOf(filterName);
        var params = Array.prototype.slice.call(arguments, 1);
        if (!params.length) {
            throw new Error('a filter is required');
        }
        params.unshift(index + 1, 0);
        Array.prototype.splice.apply(this.filters, params);
        return this;
    };
    
    Pipe.prototype.before = function(filterName) {
        var index = this.indexOf(filterName);
        var params = Array.prototype.slice.call(arguments, 1);
        if (!params.length) {
            throw new Error('a filter is required');
        }
        params.unshift(index, 0);
        Array.prototype.splice.apply(this.filters, params);
        return this;
    };
    
    Pipe.prototype.clear = function() {
        this.filters.length = 0;
        return this;
    };
    
    Pipe.prototype.shouldHaveResult = function(should) {
        if (should === false) {
            this.resultCheck = null;
            return;
        }
        if (this.resultCheck) { return; }
        var pipe = this;
        this.resultCheck = function(context) {
            if (!context.hasResult) {
                console.log(context);
                var error = new Error(pipe.name + ' failed');
                error.noResult = true;
                throw error;
            }
        };
        return this;
    };
    
    exports.Pipe = Pipe;
    },{}],16:[function(_dereq_,module,exports){
    
    var Processor = function Processor(options){
    	this.selfOptions = options;
    	this.pipes = {};
    };
    
    Processor.prototype.options = function(options) {
    	if (options) {
    		this.selfOptions = options;
    	}
    	return this.selfOptions;
    };
    
    Processor.prototype.pipe = function(name, pipe) {
    	if (typeof name === 'string') {
    		if (typeof pipe === 'undefined') {
    			return this.pipes[name];
    		} else {
    			this.pipes[name] = pipe;
    		}
    	}
    	if (name && name.name) {
    		pipe = name;
    		if (pipe.processor === this) { return pipe; }
    		this.pipes[pipe.name] = pipe;
    	}
    	pipe.processor = this;
    	return pipe;
    };
    
    Processor.prototype.process = function(input, pipe) {
    	var context = input;
    	context.options = this.options();
    	var nextPipe = pipe || input.pipe || 'default';
    	var lastPipe, lastContext;
    	while (nextPipe) {
    		if (typeof context.nextAfterChildren !== 'undefined') {
    			// children processed and coming back to parent
    			context.next = context.nextAfterChildren;
    			context.nextAfterChildren = null;
    		}
    
    		if (typeof nextPipe === 'string') {
    			nextPipe = this.pipe(nextPipe);
    		}
    		nextPipe.process(context);
    		lastContext = context;
    		lastPipe = nextPipe;
    		nextPipe = null;
    		if (context) {
    			if (context.next) {
    				context = context.next;
    				nextPipe = lastContext.nextPipe || context.pipe || lastPipe;
    			}
    		}
    	}
    	return context.hasResult ? context.result : undefined;
    };
    
    exports.Processor = Processor;
    
    },{}]},{},[8])
    (8)
    });
    /*
     * Copyright 2012 The Polymer Authors. All rights reserved.
     * Use of this source code is governed by a BSD-style
     * license that can be found in the LICENSE file.
     */
    
    if (typeof WeakMap === 'undefined') {
      (function() {
        var defineProperty = Object.defineProperty;
        var counter = Date.now() % 1e9;
    
        var WeakMap = function() {
          this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
        };
    
        WeakMap.prototype = {
          set: function(key, value) {
            var entry = key[this.name];
            if (entry && entry[0] === key)
              entry[1] = value;
            else
              defineProperty(key, this.name, {value: [key, value], writable: true});
          },
          get: function(key) {
            var entry;
            return (entry = key[this.name]) && entry[0] === key ?
                entry[1] : undefined;
          },
          delete: function(key) {
            this.set(key, undefined);
          }
        };
    
        window.WeakMap = WeakMap;
      })();
    }
    ;
    define("WeakMap", function(){});
    
    /*
     * Copyright 2012 The Polymer Authors. All rights reserved.
     * Use of this source code is goverened by a BSD-style
     * license that can be found in the LICENSE file.
     */
    
    (function(global) {
    
      var registrationsTable = new WeakMap();
    
      // We use setImmediate or postMessage for our future callback.
      var setImmediate = window.msSetImmediate;
    
      // Use post message to emulate setImmediate.
      if (!setImmediate) {
        var setImmediateQueue = [];
        var sentinel = String(Math.random());
        window.addEventListener('message', function(e) {
          if (e.data === sentinel) {
            var queue = setImmediateQueue;
            setImmediateQueue = [];
            queue.forEach(function(func) {
              func();
            });
          }
        });
        setImmediate = function(func) {
          setImmediateQueue.push(func);
          window.postMessage(sentinel, '*');
        };
      }
    
      // This is used to ensure that we never schedule 2 callas to setImmediate
      var isScheduled = false;
    
      // Keep track of observers that needs to be notified next time.
      var scheduledObservers = [];
    
      /**
       * Schedules |dispatchCallback| to be called in the future.
       * @param {MutationObserver} observer
       */
      function scheduleCallback(observer) {
        scheduledObservers.push(observer);
        if (!isScheduled) {
          isScheduled = true;
          setImmediate(dispatchCallbacks);
        }
      }
    
      function wrapIfNeeded(node) {
        return window.ShadowDOMPolyfill &&
            window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
            node;
      }
    
      function dispatchCallbacks() {
        // http://dom.spec.whatwg.org/#mutation-observers
    
        isScheduled = false; // Used to allow a new setImmediate call above.
    
        var observers = scheduledObservers;
        scheduledObservers = [];
        // Sort observers based on their creation UID (incremental).
        observers.sort(function(o1, o2) {
          return o1.uid_ - o2.uid_;
        });
    
        var anyNonEmpty = false;
        observers.forEach(function(observer) {
    
          // 2.1, 2.2
          var queue = observer.takeRecords();
          // 2.3. Remove all transient registered observers whose observer is mo.
          removeTransientObserversFor(observer);
    
          // 2.4
          if (queue.length) {
            observer.callback_(queue, observer);
            anyNonEmpty = true;
          }
        });
    
        // 3.
        if (anyNonEmpty)
          dispatchCallbacks();
      }
    
      function removeTransientObserversFor(observer) {
        observer.nodes_.forEach(function(node) {
          var registrations = registrationsTable.get(node);
          if (!registrations)
            return;
          registrations.forEach(function(registration) {
            if (registration.observer === observer)
              registration.removeTransientObservers();
          });
        });
      }
    
      /**
       * This function is used for the "For each registered observer observer (with
       * observer's options as options) in target's list of registered observers,
       * run these substeps:" and the "For each ancestor ancestor of target, and for
       * each registered observer observer (with options options) in ancestor's list
       * of registered observers, run these substeps:" part of the algorithms. The
       * |options.subtree| is checked to ensure that the callback is called
       * correctly.
       *
       * @param {Node} target
       * @param {function(MutationObserverInit):MutationRecord} callback
       */
      function forEachAncestorAndObserverEnqueueRecord(target, callback) {
        for (var node = target; node; node = node.parentNode) {
          var registrations = registrationsTable.get(node);
    
          if (registrations) {
            for (var j = 0; j < registrations.length; j++) {
              var registration = registrations[j];
              var options = registration.options;
    
              // Only target ignores subtree.
              if (node !== target && !options.subtree)
                continue;
    
              var record = callback(options);
              if (record)
                registration.enqueue(record);
            }
          }
        }
      }
    
      var uidCounter = 0;
    
      /**
       * The class that maps to the DOM MutationObserver interface.
       * @param {Function} callback.
       * @constructor
       */
      function JsMutationObserver(callback) {
        this.callback_ = callback;
        this.nodes_ = [];
        this.records_ = [];
        this.uid_ = ++uidCounter;
      }
    
      JsMutationObserver.prototype = {
        observe: function(target, options) {
          target = wrapIfNeeded(target);
    
          // 1.1
          if (!options.childList && !options.attributes && !options.characterData ||
    
              // 1.2
              options.attributeOldValue && !options.attributes ||
    
              // 1.3
              options.attributeFilter && options.attributeFilter.length &&
                  !options.attributes ||
    
              // 1.4
              options.characterDataOldValue && !options.characterData) {
    
            throw new SyntaxError();
          }
    
          var registrations = registrationsTable.get(target);
          if (!registrations)
            registrationsTable.set(target, registrations = []);
    
          // 2
          // If target's list of registered observers already includes a registered
          // observer associated with the context object, replace that registered
          // observer's options with options.
          var registration;
          for (var i = 0; i < registrations.length; i++) {
            if (registrations[i].observer === this) {
              registration = registrations[i];
              registration.removeListeners();
              registration.options = options;
              break;
            }
          }
    
          // 3.
          // Otherwise, add a new registered observer to target's list of registered
          // observers with the context object as the observer and options as the
          // options, and add target to context object's list of nodes on which it
          // is registered.
          if (!registration) {
            registration = new Registration(this, target, options);
            registrations.push(registration);
            this.nodes_.push(target);
          }
    
          registration.addListeners();
        },
    
        disconnect: function() {
          this.nodes_.forEach(function(node) {
            var registrations = registrationsTable.get(node);
            for (var i = 0; i < registrations.length; i++) {
              var registration = registrations[i];
              if (registration.observer === this) {
                registration.removeListeners();
                registrations.splice(i, 1);
                // Each node can only have one registered observer associated with
                // this observer.
                break;
              }
            }
          }, this);
          this.records_ = [];
        },
    
        takeRecords: function() {
          var copyOfRecords = this.records_;
          this.records_ = [];
          return copyOfRecords;
        }
      };
    
      /**
       * @param {string} type
       * @param {Node} target
       * @constructor
       */
      function MutationRecord(type, target) {
        this.type = type;
        this.target = target;
        this.addedNodes = [];
        this.removedNodes = [];
        this.previousSibling = null;
        this.nextSibling = null;
        this.attributeName = null;
        this.attributeNamespace = null;
        this.oldValue = null;
      }
    
      function copyMutationRecord(original) {
        var record = new MutationRecord(original.type, original.target);
        record.addedNodes = original.addedNodes.slice();
        record.removedNodes = original.removedNodes.slice();
        record.previousSibling = original.previousSibling;
        record.nextSibling = original.nextSibling;
        record.attributeName = original.attributeName;
        record.attributeNamespace = original.attributeNamespace;
        record.oldValue = original.oldValue;
        return record;
      };
    
      // We keep track of the two (possibly one) records used in a single mutation.
      var currentRecord, recordWithOldValue;
    
      /**
       * Creates a record without |oldValue| and caches it as |currentRecord| for
       * later use.
       * @param {string} oldValue
       * @return {MutationRecord}
       */
      function getRecord(type, target) {
        return currentRecord = new MutationRecord(type, target);
      }
    
      /**
       * Gets or creates a record with |oldValue| based in the |currentRecord|
       * @param {string} oldValue
       * @return {MutationRecord}
       */
      function getRecordWithOldValue(oldValue) {
        if (recordWithOldValue)
          return recordWithOldValue;
        recordWithOldValue = copyMutationRecord(currentRecord);
        recordWithOldValue.oldValue = oldValue;
        return recordWithOldValue;
      }
    
      function clearRecords() {
        currentRecord = recordWithOldValue = undefined;
      }
    
      /**
       * @param {MutationRecord} record
       * @return {boolean} Whether the record represents a record from the current
       * mutation event.
       */
      function recordRepresentsCurrentMutation(record) {
        return record === recordWithOldValue || record === currentRecord;
      }
    
      /**
       * Selects which record, if any, to replace the last record in the queue.
       * This returns |null| if no record should be replaced.
       *
       * @param {MutationRecord} lastRecord
       * @param {MutationRecord} newRecord
       * @param {MutationRecord}
       */
      function selectRecord(lastRecord, newRecord) {
        if (lastRecord === newRecord)
          return lastRecord;
    
        // Check if the the record we are adding represents the same record. If
        // so, we keep the one with the oldValue in it.
        if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
          return recordWithOldValue;
    
        return null;
      }
    
      /**
       * Class used to represent a registered observer.
       * @param {MutationObserver} observer
       * @param {Node} target
       * @param {MutationObserverInit} options
       * @constructor
       */
      function Registration(observer, target, options) {
        this.observer = observer;
        this.target = target;
        this.options = options;
        this.transientObservedNodes = [];
      }
    
      Registration.prototype = {
        enqueue: function(record) {
          var records = this.observer.records_;
          var length = records.length;
    
          // There are cases where we replace the last record with the new record.
          // For example if the record represents the same mutation we need to use
          // the one with the oldValue. If we get same record (this can happen as we
          // walk up the tree) we ignore the new record.
          if (records.length > 0) {
            var lastRecord = records[length - 1];
            var recordToReplaceLast = selectRecord(lastRecord, record);
            if (recordToReplaceLast) {
              records[length - 1] = recordToReplaceLast;
              return;
            }
          } else {
            scheduleCallback(this.observer);
          }
    
          records[length] = record;
        },
    
        addListeners: function() {
          this.addListeners_(this.target);
        },
    
        addListeners_: function(node) {
          var options = this.options;
          if (options.attributes)
            node.addEventListener('DOMAttrModified', this, true);
    
          if (options.characterData)
            node.addEventListener('DOMCharacterDataModified', this, true);
    
          if (options.childList)
            node.addEventListener('DOMNodeInserted', this, true);
    
          if (options.childList || options.subtree)
            node.addEventListener('DOMNodeRemoved', this, true);
        },
    
        removeListeners: function() {
          this.removeListeners_(this.target);
        },
    
        removeListeners_: function(node) {
          var options = this.options;
          if (options.attributes)
            node.removeEventListener('DOMAttrModified', this, true);
    
          if (options.characterData)
            node.removeEventListener('DOMCharacterDataModified', this, true);
    
          if (options.childList)
            node.removeEventListener('DOMNodeInserted', this, true);
    
          if (options.childList || options.subtree)
            node.removeEventListener('DOMNodeRemoved', this, true);
        },
    
        /**
         * Adds a transient observer on node. The transient observer gets removed
         * next time we deliver the change records.
         * @param {Node} node
         */
        addTransientObserver: function(node) {
          // Don't add transient observers on the target itself. We already have all
          // the required listeners set up on the target.
          if (node === this.target)
            return;
    
          this.addListeners_(node);
          this.transientObservedNodes.push(node);
          var registrations = registrationsTable.get(node);
          if (!registrations)
            registrationsTable.set(node, registrations = []);
    
          // We know that registrations does not contain this because we already
          // checked if node === this.target.
          registrations.push(this);
        },
    
        removeTransientObservers: function() {
          var transientObservedNodes = this.transientObservedNodes;
          this.transientObservedNodes = [];
    
          transientObservedNodes.forEach(function(node) {
            // Transient observers are never added to the target.
            this.removeListeners_(node);
    
            var registrations = registrationsTable.get(node);
            for (var i = 0; i < registrations.length; i++) {
              if (registrations[i] === this) {
                registrations.splice(i, 1);
                // Each node can only have one registered observer associated with
                // this observer.
                break;
              }
            }
          }, this);
        },
    
        handleEvent: function(e) {
          // Stop propagation since we are managing the propagation manually.
          // This means that other mutation events on the page will not work
          // correctly but that is by design.
          e.stopImmediatePropagation();
    
          switch (e.type) {
            case 'DOMAttrModified':
              // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
    
              var name = e.attrName;
              var namespace = e.relatedNode.namespaceURI;
              var target = e.target;
    
              // 1.
              var record = new getRecord('attributes', target);
              record.attributeName = name;
              record.attributeNamespace = namespace;
    
              // 2.
              var oldValue =
                  e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 3.1, 4.2
                if (!options.attributes)
                  return;
    
                // 3.2, 4.3
                if (options.attributeFilter && options.attributeFilter.length &&
                    options.attributeFilter.indexOf(name) === -1 &&
                    options.attributeFilter.indexOf(namespace) === -1) {
                  return;
                }
                // 3.3, 4.4
                if (options.attributeOldValue)
                  return getRecordWithOldValue(oldValue);
    
                // 3.4, 4.5
                return record;
              });
    
              break;
    
            case 'DOMCharacterDataModified':
              // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
              var target = e.target;
    
              // 1.
              var record = getRecord('characterData', target);
    
              // 2.
              var oldValue = e.prevValue;
    
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 3.1, 4.2
                if (!options.characterData)
                  return;
    
                // 3.2, 4.3
                if (options.characterDataOldValue)
                  return getRecordWithOldValue(oldValue);
    
                // 3.3, 4.4
                return record;
              });
    
              break;
    
            case 'DOMNodeRemoved':
              this.addTransientObserver(e.target);
              // Fall through.
            case 'DOMNodeInserted':
              // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
              var target = e.relatedNode;
              var changedNode = e.target;
              var addedNodes, removedNodes;
              if (e.type === 'DOMNodeInserted') {
                addedNodes = [changedNode];
                removedNodes = [];
              } else {
    
                addedNodes = [];
                removedNodes = [changedNode];
              }
              var previousSibling = changedNode.previousSibling;
              var nextSibling = changedNode.nextSibling;
    
              // 1.
              var record = getRecord('childList', target);
              record.addedNodes = addedNodes;
              record.removedNodes = removedNodes;
              record.previousSibling = previousSibling;
              record.nextSibling = nextSibling;
    
              forEachAncestorAndObserverEnqueueRecord(target, function(options) {
                // 2.1, 3.2
                if (!options.childList)
                  return;
    
                // 2.2, 3.3
                return record;
              });
    
          }
    
          clearRecords();
        }
      };
    
      global.JsMutationObserver = JsMutationObserver;
    
      if (!global.MutationObserver)
        global.MutationObserver = JsMutationObserver;
    
    
    })(this);
    
    define("MutationObservers", function(){});
    
    Prism.languages.markup = {
    	'comment': /&lt;!--[\w\W]*?-->/g,
    	'prolog': /&lt;\?.+?\?>/,
    	'doctype': /&lt;!DOCTYPE.+?>/,
    	'cdata': /&lt;!\[CDATA\[[\w\W]*?]]>/i,
    	'tag': {
    		pattern: /&lt;\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/gi,
    		inside: {
    			'tag': {
    				pattern: /^&lt;\/?[\w:-]+/i,
    				inside: {
    					'punctuation': /^&lt;\/?/,
    					'namespace': /^[\w-]+?:/
    				}
    			},
    			'attr-value': {
    				pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,
    				inside: {
    					'punctuation': /=|>|"/g
    				}
    			},
    			'punctuation': /\/?>/g,
    			'attr-name': {
    				pattern: /[\w:-]+/g,
    				inside: {
    					'namespace': /^[\w-]+?:/
    				}
    			}
    			
    		}
    	},
    	'entity': /&amp;#?[\da-z]{1,8};/gi
    };
    
    // Plugin to make entity title show the real entity, idea by Roman Komarov
    Prism.hooks.add('wrap', function(env) {
    
    	if (env.type === 'entity') {
    		env.attributes['title'] = env.content.replace(/&amp;/, '&');
    	}
    });
    
    define("bower-libs/prism/components/prism-markup", function(){});
    
    Prism.languages.latex = {
        // A tex command e.g. \foo
    	'keyword': /\\(?:[^a-zA-Z]|[a-zA-Z]+)/g,
        // Curly and square braces
    	'lparen': /[[({]/g,
        // Curly and square braces
    	'rparen': /[\])}]/g,
        // A comment. Tex comments start with % and go to 
        // the end of the line
    	'comment': /%.*/g,
    };
    
    define("libs/prism-latex", function(){});
    
    Prism.languages.md = (function() {
    
    	var charInsideUrl = "(&amp;|[-A-Z0-9+@#/%?=~_|[\\]()!:,.;])",
    		charEndingUrl = "(&amp;|[-A-Z0-9+@#/%=~_|[\\])])";
    	var urlPattern = new RegExp("(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi");
    	var emailPattern = /(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)/gi;
    
    	var latex = Prism.languages.latex;
    
    	var lf = /\n/gm;
    
    	var md = {};
    	md['pre gfm'] = {
    		pattern: /^`{3}.*\n(?:[\s\S]*?)\n`{3} *$/gm,
    		inside: {
    			"md md-pre": /`{3}/,
    			lf: lf
    		}
    	};
    	md['h1 alt'] = {
    		pattern: /^(.+)[ \t]*\n=+[ \t]*$/gm,
    		inside: {
    		}
    	};
    	md['h2 alt'] = {
    		pattern: /^(.+)[ \t]*\n-+[ \t]*$/gm,
    		inside: {
    		}
    	};
    	for(var i = 6; i >= 1; i--) {
    		md["h" + i] = {
    			pattern: new RegExp("^#{" + i + "}.+$", "gm"),
    			inside: {
    				"md md-hash": new RegExp("^#{" + i + "}")
    			}
    		};
    	}
    	md.li = {
    		pattern: /^[ \t]*([*+\-]|\d+\.)[ \t].+(?:\n|[ \t].*\n)*/gm,
    		inside: {
    			"md md-li": /^[ \t]*([*+\-]|\d+\.)[ \t]/m,
    			'pre gfm': {
    				pattern: /^((?: {4}|\t)+)`{3}.*\n(?:[\s\S]*?)\n\1`{3} *$/gm,
    				inside: {
    					"md md-pre": /`{3}/,
    					lf: lf
    				}
    			},
    			lf: lf
    		}
    	};
    	md.pre = {
    		pattern: /(^|(?:^|(?:^|\n)(?![ \t]*([*+\-]|\d+\.)[ \t]).*\n)\s*?\n)(\s*(?: {4}|\t).*(?:\n|$))+/g,
    		lookbehind: true,
    		inside: {
    			lf: lf
    		}
    	};
    	md.table = {
    		pattern: new RegExp(
    			[
    				'^'                         ,
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'[|]'                       , // Initial pipe
    				'(.+)\\n'                   , // $1: Header Row
    
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'[|]([ ]*[-:]+[-| :]*)\\n'  , // $2: Separator
    
    				'('                         , // $3: Table Body
    				'(?:[ ]*[|].*\\n?)*'      , // Table rows
    				')',
    				'(?:\\n|$)'                   // Stop at final newline
    			].join(''),
    			'gm'
    		),
    		inside: {
    			lf: lf
    		}
    	};
    	md['table alt'] = {
    		pattern: new RegExp(
    			[
    				'^'                         ,
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'(\\S.*[|].*)\\n'           , // $1: Header Row
    
    				'[ ]{0,3}'                  , // Allowed whitespace
    				'([-:]+[ ]*[|][-| :]*)\\n'  , // $2: Separator
    
    				'('                         , // $3: Table Body
    				'(?:.*[|].*\\n?)*'        , // Table rows
    				')'                         ,
    				'(?:\\n|$)'                   // Stop at final newline
    			].join(''),
    			'gm'
    		),
    		inside: {
    			lf: lf
    		}
    	};
    
    	md.hr = {
    		pattern: /^([*\-_] *){3,}$/gm
    	};
    	md.blockquote = {
    		pattern: /^ {0,3}> *[^\n]+$/gm,
    		inside: {
    			"md md-gt": /^ {0,3}> */,
    			"li": md.li
    		}
    	};
    	md['math block'] = {
    		pattern: /(\$\$|\\\\\[|\\\\\\\\\()[\s\S]*?(\$\$|\\\\\]|\\\\\\\\\))/g,
    		inside: {
    			"md md-bracket-start": /^(\$\$|\\\\\[|\\\\\\\\\()/,
    			"md md-bracket-end": /(\$\$|\\\\\]|\\\\\\\\\))/,
    			lf: lf,
    			rest: latex
    		}
    	};
    	md['latex block'] = {
    		pattern: /\\?\\begin\{([a-z]*\*?)\}[\s\S]*?\\?\\end\{\1\}/g,
    		inside: {
    			"keyword": /\\?\\(begin|end)/,
    			lf: lf,
    			rest: latex
    		}
    	};
    	md.fndef = {
    		pattern: /^ {0,3}\[\^.*?\]:[ \t]+.*$/gm,
    		inside: {
    			"ref-id": {
    				pattern: /\[\^.*?\]/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.linkdef = {
    		pattern: /^ {0,3}\[.*?\]:[ \t]+.*$/gm,
    		inside: {
    			"link-id": {
    				pattern: /\[.*?\]/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			url: urlPattern,
    			linktitle: /['\"\(][^\'\"\)]*['\"\)]/
    		}
    	};
    	md.p = {
    		pattern: /.+/g,
    		inside: {
    			'md md-toc': /^\s*\[(toc|TOC)\]\s*$/g
    		}
    	};
    	md.lf = /^\n$/gm;
    	md.img = {
    		pattern: /!\[[^\]]*\]\([^\)]+\)/g,
    		inside: {
    			"md md-bang": /^!/,
    			"md md-bracket-start": /\[/,
    			"md md-alt": /[^\[]+(?=\])/,
    			"md md-bracket-end": /\](?=\()/,
    			"md img-parens": {
    				pattern: /\([^\)]+\)/,
    				inside: {
    					"md md-paren-start": /^\(/,
    					"md md-title": /(['‘][^'’]*['’]|["“][^"”]*["”])(?=\)$)/,
    					"md md-src": /[^\('" \t]+(?=[\)'" \t])/,
    					"md md-paren-end": /\)$/
    				}
    			}
    		}
    	};
    	md.link = {
    		pattern: /\[(?:(\\.)|[^\[\]])*\]\([^\(\)\s]+(\(\S*?\))??[^\(\)\s]*?(\s(['‘][^'’]*['’]|["“][^"”]*["”]))?\)/gm,
    		inside: {
    			"md md-bracket-start": {
    				pattern: /(^|[^\\])\[/,
    				lookbehind: true
    			},
    			"md md-underlined-text": {
    				pattern: /(?:(\\.)|[^\[\]])+(?=\])/
    			},
    			"md md-bracket-end": /\]\s?\(/,
    			"md md-paren-end": /\)$/,
    			"md md-href": /.*/
    		}
    	};
    	md.fn = {
    		pattern: /\[\^(.*?)\]/g,
    		inside: {
    			"ref": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-ref": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.imgref = {
    		pattern: /!\[(.*?)\] ?\[(.*?)\]/g,
    		inside: {
    			"md md-bang": /^!/,
    			"ref-end": {
    				pattern: /\[[^\[\]]+\]$/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-href": /[^\[\]]+(?=]$)/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			"ref-start": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-alt": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.linkref = {
    		pattern: /\[(.*?)\] ?\[(.*?)\]/g,
    		inside: {
    			"ref-end": {
    				pattern: /\[[^\[\]]+\]$/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-href": /[^\[\]]+(?=]$)/,
    					"md md-bracket-end": /\]/
    				}
    			},
    			"ref-start": {
    				pattern: /^\[[^\[\]]+\] ?/,
    				inside: {
    					"md md-bracket-start": /\[/,
    					"md md-underlined-text": /^[^\[\]]+/,
    					"md md-bracket-end": /\]/
    				}
    			}
    		}
    	};
    	md.code = {
    		pattern: /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/g,
    		lookbehind: true,
    		inside: {
    			"md md-code": /`/
    		}
    	};
    	md.math = {
    		pattern: /\$.*?\$/g,
    		inside: {
    			"md md-bracket-start": /^\$/,
    			"md md-bracket-end": /\$$/,
    			rest: latex
    		}
    	};
    	md.strong = {
    		pattern: /([_\*])\1((?!\1{2}).)*\1{2}/g,
    		inside: {
    			"md md-strong": /([_\*])\1/g
    		}
    	};
    	md.em = {
    		pattern: /(^|[^\\])(\*|_)(\S[^\2]*?)??[^\s\\]+?\2/g,
    		lookbehind: true,
    		inside: {
    			"md md-em md-start": /^(\*|_)/,
    			"md md-em md-close": /(\*|_)$/
    		}
    	};
    	md.strike = {
    		pattern: /(^|\n|\W)(~~)(?=\S)([^\r]*?\S)\2/gm,
    		lookbehind: true,
    		inside: {
    			"md md-s": /(~~)/,
    			"md-strike-text": /[^~]+/
    		}
    	};
    	var rest = {
    		code: md.code,
    		math: md.math,
    		fn: md.fn,
    		img: md.img,
    		link: md.link,
    		imgref: md.imgref,
    		linkref: md.linkref,
    		url: urlPattern,
    		email: emailPattern,
    		strong: md.strong,
    		em: md.em,
    		strike: md.strike,
    		conflict: /⧸⧸/g,
    		comment: Prism.languages.markup.comment,
    		tag: Prism.languages.markup.tag,
    		entity: Prism.languages.markup.entity
    	};
    
    	for(var c = 6; c >= 1; c--) {
    		md["h" + c].inside.rest = rest;
    	}
    	md["h1 alt"].inside.rest = rest;
    	md["h2 alt"].inside.rest = rest;
    	md.table.inside.rest = rest;
    	md["table alt"].inside.rest = rest;
    	md.p.inside.rest = rest;
    	md.blockquote.inside.rest = rest;
    	md.li.inside.rest = rest;
    	md.fndef.inside.rest = rest;
    
    	rest = {
    		code: md.code,
    		fn: md.fn,
    		link: md.link,
    		linkref: md.linkref,
    		conflict: /⧸⧸/g,
    	};
    	md.strong.inside.rest = rest;
    	md.em.inside.rest = rest;
    	md.strike.inside.rest = rest;
    
    	var inside = {
    		code: md.code,
    		strong: md.strong,
    		em: md.em,
    		strike: md.strike,
    		conflict: /⧸⧸/g,
    		comment: Prism.languages.markup.comment,
    		tag: Prism.languages.markup.tag,
    		entity: Prism.languages.markup.entity
    	};
    	md.link.inside["md md-underlined-text"].inside = inside;
    	md.linkref.inside["ref-start"].inside["md md-underlined-text"].inside = inside;
    
    	return md;
    })();
    
    define("libs/prism-markdown", function(){});
    
    /* jshint -W084, -W099 */
    // Credit to http://dabblet.com/
    define('editor',[
    	'underscore',
    	'utils',
    	'settings',
    	'eventMgr',
    	'prism-core',
    	'diff_match_patch_uncompressed',
    	'jsondiffpatch',
    	'crel',
    	'rangy',
    	'MutationObservers',
    	'libs/prism-markdown'
    ], function( _, utils, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel, rangy) {
    
    	var editor = {};
    	var scrollTop = 0;
    	var inputElt;
    	var $inputElt;
    	var contentElt;
    	var $contentElt;
    	var marginElt;
    	var $marginElt;
    	var previewElt;
    	var pagedownEditor;
    	var trailingLfNode;
    
    	var refreshPreviewLater = (function() {
    		var elapsedTime = 0;
    		var timeoutId;
    		var refreshPreview = function() {
    			var startTime = Date.now();
    			pagedownEditor.refreshPreview();
    			elapsedTime = Date.now() - startTime;
    		};
    		if(settings.lazyRendering === true) {
    			return _.debounce(refreshPreview, 500);
    		}
    		return function() {
    			clearTimeout(timeoutId);
    			timeoutId = setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
    		};
    	})();
    	eventMgr.addListener('onPagedownConfigure', function(pagedownEditorParam) {
    		pagedownEditor = pagedownEditorParam;
    	});
    
    	var isComposing = 0;
    	eventMgr.addListener('onSectionsCreated', function(newSectionList) {
    		if(!isComposing) {
    			updateSectionList(newSectionList);
    			highlightSections();
    		}
    		if(fileChanged === true) {
    			// Refresh preview synchronously
    			pagedownEditor.refreshPreview();
    		}
    		else {
    			refreshPreviewLater();
    		}
    	});
    
    	var fileChanged = true;
    	var fileDesc;
    	eventMgr.addListener('onFileSelected', function(selectedFileDesc) {
    		fileChanged = true;
    		fileDesc = selectedFileDesc;
    	});
    
    	// Used to detect editor changes
    	function Watcher() {
    		this.isWatching = false;
    		var contentObserver;
    		this.startWatching = function() {
    			this.isWatching = true;
    			contentObserver = contentObserver || new MutationObserver(checkContentChange);
    			contentObserver.observe(contentElt, {
    				childList: true,
    				subtree: true,
    				characterData: true
    			});
    		};
    		this.stopWatching = function() {
    			contentObserver.disconnect();
    			this.isWatching = false;
    		};
    		this.noWatch = function(cb) {
    			if(this.isWatching === true) {
    				this.stopWatching();
    				cb();
    				this.startWatching();
    			}
    			else {
    				cb();
    			}
    		};
    	}
    
    	var watcher = new Watcher();
    	editor.watcher = watcher;
    
    	var diffMatchPatch = new diff_match_patch();
    	var jsonDiffPatch = jsondiffpatch.create({
    		objectHash: function(obj) {
    			return JSON.stringify(obj);
    		},
    		arrays: {
    			detectMove: false
    		},
    		textDiff: {
    			minLength: 9999999
    		}
    	});
    
    	function SelectionMgr() {
    		var self = this;
    		var lastSelectionStart = 0, lastSelectionEnd = 0;
    		this.selectionStart = 0;
    		this.selectionEnd = 0;
    		this.cursorY = 0;
    		this.adjustTop = 0;
    		this.adjustBottom = 0;
    		this.findOffsets = function(offsetList) {
    			var result = [];
    			if(!offsetList.length) {
    				return result;
    			}
    			var offset = offsetList.shift();
    			var walker = document.createTreeWalker(contentElt, 4, null, false);
    			var text = '';
    			var walkerOffset = 0;
    			while(walker.nextNode()) {
    				text = walker.currentNode.nodeValue || '';
    				var newWalkerOffset = walkerOffset + text.length;
    				while(newWalkerOffset > offset) {
    					result.push({
    						container: walker.currentNode,
    						offsetInContainer: offset - walkerOffset,
    						offset: offset
    					});
    					if(!offsetList.length) {
    						return result;
    					}
    					offset = offsetList.shift();
    				}
    				walkerOffset = newWalkerOffset;
    			}
    			do {
    				result.push({
    					container: walker.currentNode,
    					offsetInContainer: text.length,
    					offset: offset
    				});
    				offset = offsetList.shift();
    			}
    			while(offset);
    			return result;
    		};
    		this.createRange = function(start, end) {
    			start = start < 0 ? 0 : start;
    			end = end < 0 ? 0 : end;
    			var range = document.createRange();
    			var offsetList = [], startIndex, endIndex;
    			if(_.isNumber(start)) {
    				offsetList.push(start);
    				startIndex = offsetList.length - 1;
    			}
    			if(_.isNumber(end)) {
    				offsetList.push(end);
    				endIndex = offsetList.length - 1;
    			}
    			offsetList = this.findOffsets(offsetList);
    			var startOffset = _.isObject(start) ? start : offsetList[startIndex];
                try {
        			range.setStart(startOffset.container, startOffset.offsetInContainer);
                } catch(e) {};
    			var endOffset = startOffset;
    			if(end && end != start) {
    				endOffset = _.isObject(end) ? end : offsetList[endIndex];
    			}
    			range.setEnd(endOffset.container, endOffset.offsetInContainer);
    			return range;
    		};
    		var adjustScroll;
    		var debouncedUpdateCursorCoordinates = utils.debounce(function() {
    			$inputElt.toggleClass('has-selection', this.selectionStart !== this.selectionEnd);
    			var coordinates = this.getCoordinates(this.selectionEnd, this.selectionEndContainer, this.selectionEndOffset);
    			if(this.cursorY !== coordinates.y) {
    				this.cursorY = coordinates.y;
    				eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
    			}
    			if(adjustScroll) {
    				var adjustTop, adjustBottom;
    				adjustTop = adjustBottom = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
    				adjustTop = this.adjustTop || adjustTop;
    				adjustBottom = this.adjustBottom || adjustTop;
    				if(adjustTop && adjustBottom) {
    					var cursorMinY = inputElt.scrollTop + adjustTop;
    					var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjustBottom;
    					if(selectionMgr.cursorY < cursorMinY) {
    						inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
    					}
    					else if(selectionMgr.cursorY > cursorMaxY) {
    						inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
    					}
    				}
    			}
    			adjustScroll = false;
    		}, this);
    		this.updateCursorCoordinates = function(adjustScrollParam) {
    			adjustScroll = adjustScroll || adjustScrollParam;
    			debouncedUpdateCursorCoordinates();
    		};
    		this.updateSelectionRange = function() {
    			var min = Math.min(this.selectionStart, this.selectionEnd);
    			var max = Math.max(this.selectionStart, this.selectionEnd);
    			var range = this.createRange(min, max);
    			var selection = rangy.getSelection();
    			selection.removeAllRanges();
    			selection.addRange(range, this.selectionStart > this.selectionEnd);
    		};
    		var saveLastSelection = _.debounce(function() {
    			lastSelectionStart = self.selectionStart;
    			lastSelectionEnd = self.selectionEnd;
    		}, 50);
    		this.setSelectionStartEnd = function(start, end) {
    			if(start === undefined) {
    				start = this.selectionStart;
    			}
    			if(start < 0) {
    				start = 0;
    			}
    			if(end === undefined) {
    				end = this.selectionEnd;
    			}
    			if(end < 0) {
    				end = 0;
    			}
    			this.selectionStart = start;
    			this.selectionEnd = end;
    			fileDesc.editorStart = start;
    			fileDesc.editorEnd = end;
    			saveLastSelection();
    		};
    		this.saveSelectionState = (function() {
    			function save() {
    				if(fileChanged === false) {
    					var selectionStart = self.selectionStart;
    					var selectionEnd = self.selectionEnd;
    					var selection = rangy.getSelection();
    					if(selection.rangeCount > 0) {
    						var selectionRange = selection.getRangeAt(0);
    						var node = selectionRange.startContainer;
    						if((contentElt.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) {
    							var offset = selectionRange.startOffset;
    							if(node.hasChildNodes() && offset > 0) {
    								node = node.childNodes[offset - 1];
    								offset = node.textContent.length;
    							}
    							var container = node;
    							while(node != contentElt) {
    								while(node = node.previousSibling) {
    									if(node.textContent) {
    										offset += node.textContent.length;
    									}
    								}
    								node = container = container.parentNode;
    							}
    
    							if(selection.isBackwards()) {
    								selectionStart = offset + selectionRange.toString().length;
    								selectionEnd = offset;
    							}
    							else {
    								selectionStart = offset;
    								selectionEnd = offset + selectionRange.toString().length;
    							}
    
    							if(selectionStart === selectionEnd && selectionRange.startContainer.textContent == '\n' && selectionRange.startOffset == 1) {
    								// In IE if end of line is selected, offset is wrong
    								// Also, in Firefox cursor can be after the trailingLfNode
    								selectionStart = --selectionEnd;
    								self.setSelectionStartEnd(selectionStart, selectionEnd);
    								self.updateSelectionRange();
    							}
    						}
    					}
    					self.setSelectionStartEnd(selectionStart, selectionEnd);
    				}
    				undoMgr.saveSelectionState();
    			}
    
    			var nextTickAdjustScroll = false;
    			var debouncedSave = utils.debounce(function() {
    				save();
    				self.updateCursorCoordinates(nextTickAdjustScroll);
    				// In some cases we have to wait a little bit more to see the selection change (Cmd+A on Chrome/OSX)
    				longerDebouncedSave();
    			});
    			var longerDebouncedSave = utils.debounce(function() {
    				save();
    				if(lastSelectionStart === self.selectionStart && lastSelectionEnd === self.selectionEnd) {
    					nextTickAdjustScroll = false;
    				}
    				self.updateCursorCoordinates(nextTickAdjustScroll);
    				nextTickAdjustScroll = false;
    			}, 10);
    
    			return function(debounced, adjustScroll, forceAdjustScroll) {
    				if(forceAdjustScroll) {
    					lastSelectionStart = undefined;
    					lastSelectionEnd = undefined;
    				}
    				if(debounced) {
    					nextTickAdjustScroll = nextTickAdjustScroll || adjustScroll;
    					return debouncedSave();
    				}
    				else {
    					save();
    				}
    			};
    		})();
    		this.getSelectedText = function() {
    			var min = Math.min(this.selectionStart, this.selectionEnd);
    			var max = Math.max(this.selectionStart, this.selectionEnd);
    			return textContent.substring(min, max);
    		};
    		this.getCoordinates = function(inputOffset, container, offsetInContainer) {
    			if(!container) {
    				var offset = this.findOffsets([inputOffset])[0];
    				container = offset.container;
    				offsetInContainer = offset.offsetInContainer;
    			}
    			var x = 0;
    			var y = 0;
    			if(container.textContent == '\n') {
    				y = container.parentNode.offsetTop + container.parentNode.offsetHeight / 2;
    			}
    			else {
    				var selectedChar = textContent[inputOffset];
    				var startOffset = {
    					container: container,
    					offsetInContainer: offsetInContainer,
    					offset: inputOffset
    				};
    				var endOffset = {
    					container: container,
    					offsetInContainer: offsetInContainer,
    					offset: inputOffset
    				};
    				if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
    					if(startOffset.offset === 0) {
    						// Need to calculate offset-1
    						startOffset = inputOffset - 1;
    					}
    					else {
    						startOffset.offsetInContainer -= 1;
    					}
    				}
    				else {
    					if(endOffset.offset === container.textContent.length) {
    						// Need to calculate offset+1
    						endOffset = inputOffset + 1;
    					}
    					else {
    						endOffset.offsetInContainer += 1;
    					}
    				}
    				var selectionRange = this.createRange(startOffset, endOffset);
    				var selectionRect = selectionRange.getBoundingClientRect();
    				y = selectionRect.top + selectionRect.height / 2 - inputElt.getBoundingClientRect().top + inputElt.scrollTop;
    			}
    			return {
    				x: x,
    				y: y
    			};
    		};
    		this.getClosestWordOffset = function(offset) {
    			var offsetStart = 0;
    			var offsetEnd = 0;
    			var nextOffset = 0;
    			textContent.split(/\s/).some(function(word) {
    				if(word) {
    					offsetStart = nextOffset;
    					offsetEnd = nextOffset + word.length;
    					if(offsetEnd > offset) {
    						return true;
    					}
    				}
    				nextOffset += word.length + 1;
    			});
    			return {
    				start: offsetStart,
    				end: offsetEnd
    			};
    		};
    	}
    
    	var selectionMgr = new SelectionMgr();
    	editor.selectionMgr = selectionMgr;
    	$(document).on('selectionchange', '.editor-content', _.bind(selectionMgr.saveSelectionState, selectionMgr, true, false));
    
    	function adjustCursorPosition(force) {
    		if(inputElt === undefined) {
    			return;
    		}
    		selectionMgr.saveSelectionState(true, true, force);
    	}
    
    	editor.adjustCursorPosition = adjustCursorPosition;
    
    	var textContent;
    
    	function setValue(value) {
    		var startOffset = diffMatchPatch.diff_commonPrefix(textContent, value);
    		if(startOffset === textContent.length) {
    			startOffset--;
    		}
    		var endOffset = Math.min(
    			diffMatchPatch.diff_commonSuffix(textContent, value),
    				textContent.length - startOffset,
    				value.length - startOffset
    		);
    		var replacement = value.substring(startOffset, value.length - endOffset);
    		var range = selectionMgr.createRange(startOffset, textContent.length - endOffset);
    		range.deleteContents();
    		range.insertNode(document.createTextNode(replacement));
    		return {
    			start: startOffset,
    			end: value.length - endOffset
    		};
    	}
    
    	// 设置值
    	editor.setValue = setValue;
    
    	// life
    	// 为page.js的拖动编辑与预览宽度用
    	editor.onResize = function(){ 
    		eventMgr.onLayoutResize(); 
    	};
    
    	var contentQueue = false;
    	editor.setContent = function(content) {
    		if(!fileDesc) {
    			fileDesc = {content: content};
    		}
    		// 很可能还没init
    		if(contentElt) {
    			fileDesc = {content: content};
    			// log(content);
    			// log(contentElt);
    			contentElt.textContent = content;
    			// setTimeout(function(){ 
    				eventMgr.onFileSelected(fileDesc);
    			// });
    			// Force this since the content could be the same
    			checkContentChange();
    			contentQueue = false;
    		} else {
    			contentQueue = content;
    		}
    	};
    
    	window.we = editor;
    
    	function replace(selectionStart, selectionEnd, replacement) {
    		undoMgr.currentMode = undoMgr.currentMode || 'replace';
    		var range = selectionMgr.createRange(
    			Math.min(selectionStart, selectionEnd),
    			Math.max(selectionStart, selectionEnd)
    		);
    		if('' + range != replacement) {
    			range.deleteContents();
    			range.insertNode(document.createTextNode(replacement));
    		}
    		var endOffset = selectionStart + replacement.length;
    		selectionMgr.setSelectionStartEnd(endOffset, endOffset);
    		selectionMgr.updateSelectionRange();
    		selectionMgr.updateCursorCoordinates(true);
    	}
    
    	editor.replace = replace;
    
    	function replaceAll(search, replacement) {
    		undoMgr.currentMode = undoMgr.currentMode || 'replace';
    		var value = textContent.replace(search, replacement);
    		if(value != textContent) {
    			var offset = editor.setValue(value);
    			selectionMgr.setSelectionStartEnd(offset.end, offset.end);
    			selectionMgr.updateSelectionRange();
    			selectionMgr.updateCursorCoordinates(true);
    		}
    	}
    
    	editor.replaceAll = replaceAll;
    
    	function replacePreviousText(text, replacement) {
    		var offset = selectionMgr.selectionStart;
    		if(offset !== selectionMgr.selectionEnd) {
    			return false;
    		}
    		var range = selectionMgr.createRange(offset - text.length, offset);
    		if('' + range != text) {
    			return false;
    		}
    		range.deleteContents();
    		range.insertNode(document.createTextNode(replacement));
    		offset = offset - text.length + replacement.length;
    		selectionMgr.setSelectionStartEnd(offset, offset);
    		selectionMgr.updateSelectionRange();
    		selectionMgr.updateCursorCoordinates(true);
    		return true;
    	}
    
    	editor.replacePreviousText = replacePreviousText;
    
    	function setValueNoWatch(value) {
    		setValue(value);
    		textContent = value;
    	}
    
    	editor.setValueNoWatch = setValueNoWatch;
    
    	function getValue() {
    		return textContent;
    	}
    
    	editor.getValue = getValue;
    	editor.getContent = getValue;
    	
    	function focus() {
    		$contentElt.focus();
    		selectionMgr.updateSelectionRange();
    		inputElt.scrollTop = scrollTop;
    	}
    
    	editor.focus = focus;
    
    	function UndoMgr() {
    		var undoStack = [];
    		var redoStack = [];
    		var lastTime;
    		var lastMode;
    		var currentState;
    		var selectionStartBefore;
    		var selectionEndBefore;
    		this.setCommandMode = function() {
    			this.currentMode = 'command';
    		};
    		this.setMode = function() {
    		}; // For compatibility with PageDown
    		this.onButtonStateChange = function() {
    		}; // To be overridden by PageDown
    		this.saveState = utils.debounce(function() {
    			redoStack = [];
    			var currentTime = Date.now();
    			if(this.currentMode == 'comment' ||
    				this.currentMode == 'replace' ||
    				lastMode == 'newlines' ||
    				this.currentMode != lastMode ||
    				currentTime - lastTime > 1000) {
    				undoStack.push(currentState);
    				// Limit the size of the stack
    				while(undoStack.length > 100) {
    					undoStack.shift();
    				}
    			}
    			else {
    				// Restore selectionBefore that has potentially been modified by saveSelectionState
    				selectionStartBefore = currentState.selectionStartBefore;
    				selectionEndBefore = currentState.selectionEndBefore;
    			}
    			currentState = {
    				selectionStartBefore: selectionStartBefore,
    				selectionEndBefore: selectionEndBefore,
    				selectionStartAfter: selectionMgr.selectionStart,
    				selectionEndAfter: selectionMgr.selectionEnd,
    				content: textContent,
    				discussionListJSON: fileDesc.discussionListJSON
    			};
    			lastTime = currentTime;
    			lastMode = this.currentMode;
    			this.currentMode = undefined;
    			this.onButtonStateChange();
    		}, this);
    		this.saveSelectionState = _.debounce(function() {
    			// Should happen just after saveState
    			if(this.currentMode === undefined) {
    				selectionStartBefore = selectionMgr.selectionStart;
    				selectionEndBefore = selectionMgr.selectionEnd;
    			}
    		}, 50);
    		this.canUndo = function() {
    			return undoStack.length;
    		};
    		this.canRedo = function() {
    			return redoStack.length;
    		};
    		function restoreState(state, selectionStart, selectionEnd) {
    			// Update editor
    			watcher.noWatch(function() {
    				if(textContent != state.content) {
    					setValueNoWatch(state.content);
    					fileDesc.content = state.content;
    					eventMgr.onContentChanged(fileDesc, state.content);
    				}
    				selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates(true);
    				var discussionListJSON = fileDesc.discussionListJSON;
    				if(discussionListJSON != state.discussionListJSON) {
    					var oldDiscussionList = fileDesc.discussionList;
    					fileDesc.discussionListJSON = state.discussionListJSON;
    					var newDiscussionList = fileDesc.discussionList;
    					var diff = jsonDiffPatch.diff(oldDiscussionList, newDiscussionList);
    					var commentsChanged = false;
    					_.each(diff, function(discussionDiff, discussionIndex) {
    						if(!_.isArray(discussionDiff)) {
    							commentsChanged = true;
    						}
    						else if(discussionDiff.length === 1) {
    							eventMgr.onDiscussionCreated(fileDesc, newDiscussionList[discussionIndex]);
    						}
    						else {
    							eventMgr.onDiscussionRemoved(fileDesc, oldDiscussionList[discussionIndex]);
    						}
    					});
    					commentsChanged && eventMgr.onCommentsChanged(fileDesc);
    				}
    			});
    
    			selectionStartBefore = selectionStart;
    			selectionEndBefore = selectionEnd;
    			currentState = state;
    			this.currentMode = undefined;
    			lastMode = undefined;
    			this.onButtonStateChange();
    			adjustCursorPosition();
    		}
    
    		this.undo = function() {
    			var state = undoStack.pop();
    			if(!state) {
    				return;
    			}
    			redoStack.push(currentState);
    			restoreState.call(this, state, currentState.selectionStartBefore, currentState.selectionEndBefore);
    		};
    		this.redo = function() {
    			var state = redoStack.pop();
    			if(!state) {
    				return;
    			}
    			undoStack.push(currentState);
    			restoreState.call(this, state, state.selectionStartAfter, state.selectionEndAfter);
    		};
    		this.init = function() {
    			var content = fileDesc.content;
    			undoStack = [];
    			redoStack = [];
    			lastTime = 0;
    			currentState = {
    				selectionStartAfter: fileDesc.selectionStart,
    				selectionEndAfter: fileDesc.selectionEnd,
    				content: content,
    				discussionListJSON: fileDesc.discussionListJSON
    			};
    			this.currentMode = undefined;
    			lastMode = undefined;
    			contentElt.textContent = content;
    			// Force this since the content could be the same
    			checkContentChange();
    		};
    	}
    
    	var undoMgr = new UndoMgr();
    	editor.undoMgr = undoMgr;
    
    	function onComment() {
    		if(watcher.isWatching === true) {
    			undoMgr.currentMode = undoMgr.currentMode || 'comment';
    			undoMgr.saveState();
    		}
    	}
    
    	eventMgr.addListener('onDiscussionCreated', onComment);
    	eventMgr.addListener('onDiscussionRemoved', onComment);
    	eventMgr.addListener('onCommentsChanged', onComment);
    
    	var triggerSpellCheck = _.debounce(function() {
    		var selection = window.getSelection();
    		if(!selectionMgr.hasFocus || isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) {
    			return;
    		}
    		// Hack for Chrome to trigger the spell checker
    		if(selectionMgr.selectionStart) {
    			selection.modify("move", "backward", "character");
    			selection.modify("move", "forward", "character");
    		}
    		else {
    			selection.modify("move", "forward", "character");
    			selection.modify("move", "backward", "character");
    		}
    	}, 10);
    
    	function checkContentChange() {
    		var newTextContent = inputElt.textContent;
    		if(contentElt.lastChild === trailingLfNode && trailingLfNode.textContent.slice(-1) == '\n') {
    			newTextContent = newTextContent.slice(0, -1);
    		}
    		newTextContent = newTextContent.replace(/\r\n?/g, '\n'); // Mac/DOS to Unix
    
    		if(fileChanged === false) {
    			if(newTextContent == textContent) {
    				// User has removed the empty section
    				if(contentElt.children.length === 0) {
    					contentElt.innerHTML = '';
    					sectionList.forEach(function(section) {
    						contentElt.appendChild(section.elt);
    					});
    					addTrailingLfNode();
    				}
    				return;
    			}
    			undoMgr.currentMode = undoMgr.currentMode || 'typing';
    			var discussionList = _.values(fileDesc.discussionList);
    			fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion);
    			var updateDiscussionList = adjustCommentOffsets(textContent, newTextContent, discussionList);
    			textContent = newTextContent;
    			if(updateDiscussionList === true) {
    				fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage
    			}
    			fileDesc.content = textContent;
    			selectionMgr.saveSelectionState();
    			eventMgr.onContentChanged(fileDesc, textContent);
    			updateDiscussionList && eventMgr.onCommentsChanged(fileDesc);
    			undoMgr.saveState();
    			triggerSpellCheck();
    		}
    		else {
    			textContent = newTextContent;
    			fileDesc.content = textContent;
    			selectionMgr.setSelectionStartEnd(fileDesc.editorStart, fileDesc.editorEnd);
    			selectionMgr.updateSelectionRange();
    			selectionMgr.updateCursorCoordinates();
    			undoMgr.saveSelectionState();
    			eventMgr.onFileOpen(fileDesc, textContent);
    			previewElt.scrollTop = fileDesc.previewScrollTop;
    			scrollTop = fileDesc.editorScrollTop;
    			inputElt.scrollTop = scrollTop;
    			fileChanged = false;
    		}
    	}
    
    	function adjustCommentOffsets(oldTextContent, newTextContent, discussionList) {
    		if(!discussionList.length) {
    			return;
    		}
    		var changes = diffMatchPatch.diff_main(oldTextContent, newTextContent);
    		var changed = false;
    		var startOffset = 0;
    		changes.forEach(function(change) {
    			var changeType = change[0];
    			var changeText = change[1];
    			if(changeType === 0) {
    				startOffset += changeText.length;
    				return;
    			}
    			var endOffset = startOffset;
    			var diffOffset = changeText.length;
    			if(changeType === -1) {
    				endOffset += diffOffset;
    				diffOffset = -diffOffset;
    			}
    			discussionList.forEach(function(discussion) {
    				// selectionEnd
    				if(discussion.selectionEnd > endOffset) {
    					discussion.selectionEnd += diffOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				else if(discussion.selectionEnd > startOffset) {
    					discussion.selectionEnd = startOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				// selectionStart
    				if(discussion.selectionStart >= endOffset) {
    					discussion.selectionStart += diffOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    				else if(discussion.selectionStart > startOffset) {
    					discussion.selectionStart = startOffset;
    					discussion.discussionIndex && (changed = true);
    				}
    			});
    			if(changeType === 1) {
    				startOffset += changeText.length;
    			}
    		});
    		return changed;
    	}
    
    	editor.adjustCommentOffsets = adjustCommentOffsets;
    
    	// 入口
    	editor.init = function() {
    		inputElt = document.getElementById('wmd-input');
    		$inputElt = $(inputElt);
    		contentElt = inputElt.querySelector('.editor-content');
    		$contentElt = $(contentElt);
    		marginElt = inputElt.querySelector('.editor-margin');
    		$marginElt = $(marginElt);
    		previewElt = document.querySelector('.preview-container');
    
    		$inputElt.addClass(settings.editorFontClass);
    
    		watcher.startWatching();
    
    		$(inputElt).scroll(function() {
    			scrollTop = inputElt.scrollTop;
    			if(fileChanged === false) {
    				fileDesc.editorScrollTop = scrollTop;
    			}
    		});
    		$(previewElt).scroll(function() {
    			if(fileChanged === false) {
    				fileDesc.previewScrollTop = previewElt.scrollTop;
    			}
    		});
    
    		// See https://gist.github.com/shimondoodkin/1081133
            // life 之前插入到html中, 现在插入到body中, 且width, height = 0
    		if(/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)) {
    			var $editableFix = $('<input style="width:0px;height:0px;border:none;margin:0;padding:0;" tabIndex="-1">').appendTo('body');
    			$contentElt.blur(function() {
    				$editableFix[0].setSelectionRange(0, 0);
    				$editableFix.blur();
    			});
    		}
    
    		inputElt.focus = focus;
    		inputElt.adjustCursorPosition = adjustCursorPosition;
    
    		Object.defineProperty(inputElt, 'value', {
    			get: function() {
    				return textContent;
    			},
    			set: setValue
    		});
    
    		Object.defineProperty(inputElt, 'selectionStart', {
    			get: function() {
    				return Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			},
    			set: function(value) {
    				selectionMgr.setSelectionStartEnd(value);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates();
    			},
    
    			enumerable: true,
    			configurable: true
    		});
    
    		Object.defineProperty(inputElt, 'selectionEnd', {
    			get: function() {
    				return Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			},
    			set: function(value) {
    				selectionMgr.setSelectionStartEnd(undefined, value);
    				selectionMgr.updateSelectionRange();
    				selectionMgr.updateCursorCoordinates();
    			},
    
    			enumerable: true,
    			configurable: true
    		});
    
    		var clearNewline = false;
    		$contentElt
    			.on('keydown', function(evt) {
    				if(
    					evt.which === 17 || // Ctrl
    					evt.which === 91 || // Cmd
    					evt.which === 18 || // Alt
    					evt.which === 16 // Shift
    					) {
    					return;
    				}
    				selectionMgr.saveSelectionState();
    				adjustCursorPosition();
    
    				var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
    
    				switch(evt.which) {
    					case 9: // Tab
    						if(!cmdOrCtrl) {
    							action('indent', {
    								inverse: evt.shiftKey
    							});
    							evt.preventDefault();
    						}
    						break;
    					case 13:
    						action('newline');
    						evt.preventDefault();
    						break;
    				}
    				if(evt.which !== 13) {
    					clearNewline = false;
    				}
    			})
    			.on('compositionstart', function() {
    				isComposing++;
    			})
    			.on('compositionend', function() {
    				setTimeout(function() {
    					isComposing--;
    				}, 0);
    			})
    			.on('mouseup', _.bind(selectionMgr.saveSelectionState, selectionMgr, true, false))
    			.on('paste', function(evt) {
    				undoMgr.currentMode = 'paste';
    				evt.preventDefault();
    				var data, clipboardData = (evt.originalEvent || evt).clipboardData;
    				if(clipboardData) {
    					data = clipboardData.getData('text/plain');
    				}
    				else {
    					clipboardData = window.clipboardData;
    					data = clipboardData && clipboardData.getData('Text');
    				}
    				if(!data) {
    					return;
    				}
    				replace(selectionMgr.selectionStart, selectionMgr.selectionEnd, data);
    				adjustCursorPosition();
    			})
    			.on('cut', function() {
    				undoMgr.currentMode = 'cut';
    				adjustCursorPosition();
    			})
    			.on('focus', function() {
    				selectionMgr.hasFocus = true;
    			})
    			.on('blur', function() {
    				selectionMgr.hasFocus = false;
    			});
    
    		var action = function(action, options) {
    			var textContent = getValue();
    			var min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			var max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
    			var state = {
    				selectionStart: min,
    				selectionEnd: max,
    				before: textContent.slice(0, min),
    				after: textContent.slice(max),
    				selection: textContent.slice(min, max)
    			};
    
    			actions[action](state, options || {});
    			setValue(state.before + state.selection + state.after);
    			selectionMgr.setSelectionStartEnd(state.selectionStart, state.selectionEnd);
    			selectionMgr.updateSelectionRange();
    		};
    
    		var indentRegex = /^ {0,3}>[ ]*|^[ \t]*(?:[*+\-]|(\d+)\.)[ \t]|^\s+/;
    		var actions = {
    			indent: function(state, options) {
    				function strSplice(str, i, remove, add) {
    					remove = +remove || 0;
    					add = add || '';
    					return str.slice(0, i) + add + str.slice(i + remove);
    				}
    
    				var lf = state.before.lastIndexOf('\n') + 1;
    				if(options.inverse) {
    					if(/\s/.test(state.before.charAt(lf))) {
    						state.before = strSplice(state.before, lf, 1);
    
    						state.selectionStart--;
    						state.selectionEnd--;
    					}
    					state.selection = state.selection.replace(/^[ \t]/gm, '');
    				} else {
    					var previousLine = state.before.slice(lf);
    					if(state.selection || previousLine.match(indentRegex)) {
    						state.before = strSplice(state.before, lf, 0, '\t');
    						state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
    						state.selectionStart++;
    						state.selectionEnd++;
    					} else {
    						state.before += '\t';
    						state.selectionStart++;
    						state.selectionEnd++;
    						return;
    					}
    				}
    
    				state.selectionEnd = state.selectionStart + state.selection.length;
    			},
    
    			newline: function(state) {
    				var lf = state.before.lastIndexOf('\n') + 1;
    				if(clearNewline) {
    					state.before = state.before.substring(0, lf);
    					state.selection = '';
    					state.selectionStart = lf;
    					state.selectionEnd = lf;
    					clearNewline = false;
    					return;
    				}
    				clearNewline = false;
    				var previousLine = state.before.slice(lf);
    				var indentMatch = previousLine.match(indentRegex);
    				var indent = (indentMatch || [''])[0];
    				if(indentMatch && indentMatch[1]) {
    					var number = parseInt(indentMatch[1], 10);
    					indent = indent.replace(/\d+/, number + 1);
    				}
    				if(indent.length) {
    					clearNewline = true;
    				}
    
    				undoMgr.currentMode = 'newlines';
    
    				state.before += '\n' + indent;
    				state.selection = '';
    				state.selectionStart += indent.length + 1;
    				state.selectionEnd = state.selectionStart;
    			}
    		};
    
    		// 在init之前就有设置值的命令
    		if(contentQueue !== false) {
    			editor.setContent(contentQueue);
    		}
    	};
    
    	var sectionList = [];
    	var sectionsToRemove = [];
    	var modifiedSections = [];
    	var insertBeforeSection;
    
    	function updateSectionList(newSectionList) {
    
    		modifiedSections = [];
    		sectionsToRemove = [];
    		insertBeforeSection = undefined;
    
    		// Render everything if file changed
    		if(fileChanged === true) {
    			sectionsToRemove = sectionList;
    			sectionList = newSectionList;
    			modifiedSections = newSectionList;
    			return;
    		}
    
    		// Find modified section starting from top
    		var leftIndex = sectionList.length;
    		_.some(sectionList, function(section, index) {
    			var newSection = newSectionList[index];
    			if(index >= newSectionList.length ||
    				// Check modified
    				section.textWithFrontMatter != newSection.textWithFrontMatter ||
    				// Check that section has not been detached or moved
    				section.elt.parentNode !== contentElt ||
    				// Check also the content since nodes can be injected in sections via copy/paste
    				section.elt.textContent != newSection.textWithFrontMatter) {
    				leftIndex = index;
    				return true;
    			}
    		});
    
    		// Find modified section starting from bottom
    		var rightIndex = -sectionList.length;
    		_.some(sectionList.slice().reverse(), function(section, index) {
    			var newSection = newSectionList[newSectionList.length - index - 1];
    			if(index >= newSectionList.length ||
    				// Check modified
    				section.textWithFrontMatter != newSection.textWithFrontMatter ||
    				// Check that section has not been detached or moved
    				section.elt.parentNode !== contentElt ||
    				// Check also the content since nodes can be injected in sections via copy/paste
    				section.elt.textContent != newSection.textWithFrontMatter) {
    				rightIndex = -index;
    				return true;
    			}
    		});
    
    		if(leftIndex - rightIndex > sectionList.length) {
    			// Prevent overlap
    			rightIndex = leftIndex - sectionList.length;
    		}
    
    		// Create an array composed of left unmodified, modified, right
    		// unmodified sections
    		var leftSections = sectionList.slice(0, leftIndex);
    		modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
    		var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
    		insertBeforeSection = _.first(rightSections);
    		sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
    		sectionList = leftSections.concat(modifiedSections).concat(rightSections);
    	}
    
    	// 高亮预览
    	function highlightSections() {
    		var newSectionEltList = document.createDocumentFragment();
    		modifiedSections.forEach(function(section) {
    			// 高亮之
    			highlight(section);
    			newSectionEltList.appendChild(section.elt);
    		});
    		watcher.noWatch(function() {
    			if(fileChanged === true) {
    				contentElt.innerHTML = '';
    				contentElt.appendChild(newSectionEltList);
    			}
    			else {
    				// Remove outdated sections
    				sectionsToRemove.forEach(function(section) {
    					// section may be already removed
    					section.elt.parentNode === contentElt && contentElt.removeChild(section.elt);
    					// To detect sections that come back with built-in undo
    					section.elt.generated = false;
    				});
    
    				if(insertBeforeSection !== undefined) {
    					contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
    				}
    				else {
    					contentElt.appendChild(newSectionEltList);
    				}
    
    				// Remove unauthorized nodes (text nodes outside of sections or duplicated sections via copy/paste)
    				var childNode = contentElt.firstChild;
    				while(childNode) {
    					var nextNode = childNode.nextSibling;
    					if(!childNode.generated) {
    						contentElt.removeChild(childNode);
    					}
    					childNode = nextNode;
    				}
    			}
    			addTrailingLfNode();
    			selectionMgr.updateSelectionRange();
    			selectionMgr.updateCursorCoordinates();
    		});
    	}
    
    	function addTrailingLfNode() {
    		trailingLfNode = crel('span', {
    			class: 'token lf'
    		});
    		trailingLfNode.textContent = '\n';
    		contentElt.appendChild(trailingLfNode);
    	}
    
    	var escape = (function() {
    		var entityMap = {
    			"&": "&amp;",
    			"<": "&lt;",
    			"\u00a0": ' '
    		};
    		return function(str) {
    			return str.replace(/[&<\u00a0]/g, function(s) {
    				return entityMap[s];
    			});
    		};
    	})();
    
    	// 高亮用户所输入的
    	// 实现编辑器下预览
    	function highlight(section) {
    		var text = escape(section.text);
    		if(!window.viewerMode) {
    			// log("pre")
    			// log(text);
    			// # lif
    			text = Prism.highlight(text, Prism.languages.md);
    			// log('after');
    			// <span class="token h1" ><span class="token md md-hash" >#</span> lif</span>
    			// log(text);
    		}
    		var frontMatter = section.textWithFrontMatter.substring(0, section.textWithFrontMatter.length - section.text.length);
    		if(frontMatter.length) {
    			// Front matter highlighting
    			frontMatter = escape(frontMatter);
    			frontMatter = frontMatter.replace(/\n/g, '<span class="token lf">\n</span>');
    			text = '<span class="token md">' + frontMatter + '</span>' + text;
    		}
    		var sectionElt = crel('span', {
    			id: 'wmd-input-section-' + section.id,
    			class: 'wmd-input-section'
    		});
    		sectionElt.generated = true;
    		sectionElt.innerHTML = text;
    		section.elt = sectionElt;
    	}
    
    	eventMgr.onEditorCreated(editor);
    	return editor;
    });
    
    // needs Markdown.Converter.js at the moment
    
    (function () {
    
        var util = {},
            position = {},
            ui = {},
            doc = window.document,
            re = window.RegExp,
            nav = window.navigator,
            SETTINGS = { lineLength: 72 },
    
        // Used to work around some browser bugs where we can't use feature testing.
            uaSniffed = {
                isIE: /msie/.test(nav.userAgent.toLowerCase()),
                isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()),
                isOpera: /opera/.test(nav.userAgent.toLowerCase())
            };
    
        var defaultsStrings = {
            bold: "Strong <strong> Ctrl/Cmd+B",
            boldexample: "strong text",
    
            italic: "Emphasis <em> Ctrl/Cmd+I",
            italicexample: "emphasized text",
    
            link: "Hyperlink <a> Ctrl/Cmd+L",
            linkdescription: "enter link description here",
            linkdialog: "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>",
    
            quote: "Blockquote <blockquote> Ctrl/Cmd+Q",
            quoteexample: "Blockquote",
    
            code: "Code Sample <pre><code> Ctrl/Cmd+K",
            codeexample: "enter code here",
    
            image: "Image <img> Ctrl/Cmd+G",
            imagedescription: "enter image description here",
            imagedialog: "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>",
    
            olist: "Numbered List <ol> Ctrl/Cmd+O",
            ulist: "Bulleted List <ul> Ctrl/Cmd+U",
            litem: "List item",
    
            heading: "Heading <h1>/<h2> Ctrl/Cmd+H",
            headingexample: "Heading",
    
            hr: "Horizontal Rule <hr> Ctrl/Cmd+R",
    
            undo: "Undo - Ctrl/Cmd+Z",
            redo: "Redo - Ctrl/Cmd+Y",
    
            help: "Markdown Editing Help"
        };
    
    
        // -------------------------------------------------------------------
        //  YOUR CHANGES GO HERE
        //
        // I've tried to localize the things you are likely to change to
        // this area.
        // -------------------------------------------------------------------
    
        // The default text that appears in the dialog input box when entering
        // links.
        var imageDefaultText = "http://";
        var linkDefaultText = "http://";
    
        // -------------------------------------------------------------------
        //  END OF YOUR CHANGES
        // -------------------------------------------------------------------
    
        // options, if given, can have the following properties:
        //   options.helpButton = { handler: yourEventHandler }
        //   options.strings = { italicexample: "slanted text" }
        // `yourEventHandler` is the click handler for the help button.
        // If `options.helpButton` isn't given, not help button is created.
        // `options.strings` can have any or all of the same properties as
        // `defaultStrings` above, so you can just override some string displayed
        // to the user on a case-by-case basis, or translate all strings to
        // a different language.
        //
        // For backwards compatibility reasons, the `options` argument can also
        // be just the `helpButton` object, and `strings.help` can also be set via
        // `helpButton.title`. This should be considered legacy.
        //
        // The constructed editor object has the methods:
        // - getConverter() returns the markdown converter object that was passed to the constructor
        // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
        // - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
        Markdown.Editor = function (markdownConverter, idPostfix, options) {
    
            options = options || {};
    
            if (typeof options.handler === "function") { //backwards compatible behavior
                options = { helpButton: options };
            }
            options.strings = options.strings || {};
            if (options.helpButton) {
                options.strings.help = options.strings.help || options.helpButton.title;
            }
            var getString = function (identifier) { return options.strings[identifier] || defaultsStrings[identifier]; }
    
            idPostfix = idPostfix || "";
    
            var hooks = this.hooks = new Markdown.HookCollection();
            hooks.addNoop("onPreviewRefresh");       // called with no arguments after the preview has been refreshed
            hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text
            hooks.addFalse("insertImageDialog");     /* called with one parameter: a callback to be called with the URL of the image. If the application creates
                                                      * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
                                                      * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
                                                      */
            hooks.addFalse("insertLinkDialog");
    
            this.getConverter = function () { return markdownConverter; }
    
            var that = this,
                panels;
    
            var undoManager;
            this.run = function () {
                if (panels)
                    return; // already initialized
    
                panels = new PanelCollection(idPostfix);
                var commandManager = new CommandManager(hooks, getString);
                var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); });
                var uiManager;
    
                if(options.undoManager) {
                    undoManager = options.undoManager;
                    undoManager.onButtonStateChange = function() {
                        uiManager.setUndoRedoButtonStates();
                    };
                    if (uiManager) // not available on the first call
                        uiManager.setUndoRedoButtonStates();
                }
                else if (!/\?noundo/.test(doc.location.href)) {
                    undoManager = new UndoManager(function () {
                        previewManager.refresh();
                        if (uiManager) // not available on the first call
                            uiManager.setUndoRedoButtonStates();
                    }, panels);
                    this.textOperation = function (f) {
                        undoManager.setCommandMode();
                        f();
                        that.refreshPreview();
                    }
                }
    
                uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, options.helpButton, getString);
                uiManager.setUndoRedoButtonStates();
    
                var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
    
                //Not necessary
                //forceRefresh();
                that.undoManager = undoManager;
                that.uiManager = uiManager;
            };
    
        }
    
        // before: contains all the text in the input box BEFORE the selection.
        // after: contains all the text in the input box AFTER the selection.
        function Chunks() { }
    
        // startRegex: a regular expression to find the start tag
        // endRegex: a regular expresssion to find the end tag
        Chunks.prototype.findTags = function (startRegex, endRegex) {
    
            var chunkObj = this;
            var regex;
    
            if (startRegex) {
    
                regex = util.extendRegExp(startRegex, "", "$");
    
                this.before = this.before.replace(regex,
                    function (match) {
                        chunkObj.startTag = chunkObj.startTag + match;
                        return "";
                    });
    
                regex = util.extendRegExp(startRegex, "^", "");
    
                this.selection = this.selection.replace(regex,
                    function (match) {
                        chunkObj.startTag = chunkObj.startTag + match;
                        return "";
                    });
            }
    
            if (endRegex) {
    
                regex = util.extendRegExp(endRegex, "", "$");
    
                this.selection = this.selection.replace(regex,
                    function (match) {
                        chunkObj.endTag = match + chunkObj.endTag;
                        return "";
                    });
    
                regex = util.extendRegExp(endRegex, "^", "");
    
                this.after = this.after.replace(regex,
                    function (match) {
                        chunkObj.endTag = match + chunkObj.endTag;
                        return "";
                    });
            }
        };
    
        // If remove is false, the whitespace is transferred
        // to the before/after regions.
        //
        // If remove is true, the whitespace disappears.
        Chunks.prototype.trimWhitespace = function (remove) {
            var beforeReplacer, afterReplacer, that = this;
            if (remove) {
                beforeReplacer = afterReplacer = "";
            } else {
                beforeReplacer = function (s) { that.before += s; return ""; }
                afterReplacer = function (s) { that.after = s + that.after; return ""; }
            }
    
            this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer);
        };
    
    
        Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) {
    
            if (nLinesBefore === undefined) {
                nLinesBefore = 1;
            }
    
            if (nLinesAfter === undefined) {
                nLinesAfter = 1;
            }
    
            nLinesBefore++;
            nLinesAfter++;
    
            var regexText;
            var replacementText;
    
            // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985
            if (navigator.userAgent.match(/Chrome/)) {
                "X".match(/()./);
            }
    
            this.selection = this.selection.replace(/(^\n*)/, "");
    
            this.startTag = this.startTag + re.$1;
    
            this.selection = this.selection.replace(/(\n*$)/, "");
            this.endTag = this.endTag + re.$1;
            this.startTag = this.startTag.replace(/(^\n*)/, "");
            this.before = this.before + re.$1;
            this.endTag = this.endTag.replace(/(\n*$)/, "");
            this.after = this.after + re.$1;
    
            if (this.before) {
    
                regexText = replacementText = "";
    
                while (nLinesBefore--) {
                    regexText += "\\n?";
                    replacementText += "\n";
                }
    
                if (findExtraNewlines) {
                    regexText = "\\n*";
                }
                this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
            }
    
            if (this.after) {
    
                regexText = replacementText = "";
    
                while (nLinesAfter--) {
                    regexText += "\\n?";
                    replacementText += "\n";
                }
                if (findExtraNewlines) {
                    regexText = "\\n*";
                }
    
                this.after = this.after.replace(new re(regexText, ""), replacementText);
            }
        };
    
        // end of Chunks
    
        // A collection of the important regions on the page.
        // Cached so we don't have to keep traversing the DOM.
        // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around
        // this issue:
        // Internet explorer has problems with CSS sprite buttons that use HTML
        // lists.  When you click on the background image "button", IE will
        // select the non-existent link text and discard the selection in the
        // textarea.  The solution to this is to cache the textarea selection
        // on the button's mousedown event and set a flag.  In the part of the
        // code where we need to grab the selection, we check for the flag
        // and, if it's set, use the cached area instead of querying the
        // textarea.
        //
        // This ONLY affects Internet Explorer (tested on versions 6, 7
        // and 8) and ONLY on button clicks.  Keyboard shortcuts work
        // normally since the focus never leaves the textarea.
        function PanelCollection(postfix) {
            this.buttonBar = doc.getElementById("wmd-button-bar" + postfix);
            this.preview = doc.getElementById("wmd-preview" + postfix);
            this.input = doc.getElementById("wmd-input" + postfix);
        };
    
        // Returns true if the DOM element is visible, false if it's hidden.
        // Checks if display is anything other than none.
        util.isVisible = function (elem) {
    
            if (window.getComputedStyle) {
                // Most browsers
                return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none";
            }
            else if (elem.currentStyle) {
                // IE
                return elem.currentStyle["display"] !== "none";
            }
        };
    
    
        // Adds a listener callback to a DOM element which is fired on a specified
        // event.
        util.addEvent = function (elem, event, listener) {
            if (elem.attachEvent) {
                // IE only.  The "on" is mandatory.
                elem.attachEvent("on" + event, listener);
            }
            else {
                // Other browsers.
                elem.addEventListener(event, listener, false);
            }
        };
    
    
        // Removes a listener callback from a DOM element which is fired on a specified
        // event.
        util.removeEvent = function (elem, event, listener) {
            if (elem.detachEvent) {
                // IE only.  The "on" is mandatory.
                elem.detachEvent("on" + event, listener);
            }
            else {
                // Other browsers.
                elem.removeEventListener(event, listener, false);
            }
        };
    
        // Converts \r\n and \r to \n.
        util.fixEolChars = function (text) {
            text = text.replace(/\r\n/g, "\n");
            text = text.replace(/\r/g, "\n");
            return text;
        };
    
        // Extends a regular expression.  Returns a new RegExp
        // using pre + regex + post as the expression.
        // Used in a few functions where we have a base
        // expression and we want to pre- or append some
        // conditions to it (e.g. adding "$" to the end).
        // The flags are unchanged.
        //
        // regex is a RegExp, pre and post are strings.
        util.extendRegExp = function (regex, pre, post) {
    
            if (pre === null || pre === undefined) {
                pre = "";
            }
            if (post === null || post === undefined) {
                post = "";
            }
    
            var pattern = regex.toString();
            var flags;
    
            // Replace the flags with empty space and store them.
            pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) {
                flags = flagsPart;
                return "";
            });
    
            // Remove the slash delimiters on the regular expression.
            pattern = pattern.replace(/(^\/|\/$)/g, "");
            pattern = pre + pattern + post;
    
            return new re(pattern, flags);
        }
    
        // UNFINISHED
        // The assignment in the while loop makes jslint cranky.
        // I'll change it to a better loop later.
        position.getTop = function (elem, isInner) {
            var result = elem.offsetTop;
            if (!isInner) {
                while (elem = elem.offsetParent) {
                    result += elem.offsetTop;
                }
            }
            return result;
        };
    
        position.getHeight = function (elem) {
            return elem.offsetHeight || elem.scrollHeight;
        };
    
        position.getWidth = function (elem) {
            return elem.offsetWidth || elem.scrollWidth;
        };
    
        position.getPageSize = function () {
    
            var scrollWidth, scrollHeight;
            var innerWidth, innerHeight;
    
            // It's not very clear which blocks work with which browsers.
            if (self.innerHeight && self.scrollMaxY) {
                scrollWidth = doc.body.scrollWidth;
                scrollHeight = self.innerHeight + self.scrollMaxY;
            }
            else if (doc.body.scrollHeight > doc.body.offsetHeight) {
                scrollWidth = doc.body.scrollWidth;
                scrollHeight = doc.body.scrollHeight;
            }
            else {
                scrollWidth = doc.body.offsetWidth;
                scrollHeight = doc.body.offsetHeight;
            }
    
            if (self.innerHeight) {
                // Non-IE browser
                innerWidth = self.innerWidth;
                innerHeight = self.innerHeight;
            }
            else if (doc.documentElement && doc.documentElement.clientHeight) {
                // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
                innerWidth = doc.documentElement.clientWidth;
                innerHeight = doc.documentElement.clientHeight;
            }
            else if (doc.body) {
                // Other versions of IE
                innerWidth = doc.body.clientWidth;
                innerHeight = doc.body.clientHeight;
            }
    
            var maxWidth = Math.max(scrollWidth, innerWidth);
            var maxHeight = Math.max(scrollHeight, innerHeight);
            return [maxWidth, maxHeight, innerWidth, innerHeight];
        };
    
        // Handles pushing and popping TextareaStates for undo/redo commands.
        // I should rename the stack variables to list.
        function UndoManager(callback, panels) {
    
            var undoObj = this;
            var undoStack = []; // A stack of undo states
            var stackPtr = 0; // The index of the current state
            var mode = "none";
            var lastState; // The last state
            var timer; // The setTimeout handle for cancelling the timer
            var inputStateObj;
    
            // Set the mode for later logic steps.
            var setMode = function (newMode, noSave) {
                if (mode != newMode) {
                    mode = newMode;
                    if (!noSave) {
                        saveState();
                    }
                }
    
                if (!uaSniffed.isIE || mode != "moving") {
                    timer = setTimeout(refreshState, 1);
                }
                else {
                    inputStateObj = null;
                }
            };
    
            var refreshState = function (isInitialState) {
                inputStateObj = new TextareaState(panels, isInitialState);
                timer = undefined;
            };
    
            this.setCommandMode = function () {
                mode = "command";
                saveState();
                timer = setTimeout(refreshState, 0);
            };
    
            this.canUndo = function () {
                return stackPtr > 1;
            };
    
            this.canRedo = function () {
                if (undoStack[stackPtr + 1]) {
                    return true;
                }
                return false;
            };
    
            // Removes the last state and restores it.
            this.undo = function () {
    
                if (undoObj.canUndo()) {
                    if (lastState) {
                        // What about setting state -1 to null or checking for undefined?
                        lastState.restore();
                        lastState = null;
                    }
                    else {
                        undoStack[stackPtr] = new TextareaState(panels);
                        undoStack[--stackPtr].restore();
    
                        if (callback) {
                            callback();
                        }
                    }
                }
    
                mode = "none";
                panels.input.focus();
                refreshState();
            };
    
            // Redo an action.
            this.redo = function () {
    
                if (undoObj.canRedo()) {
    
                    undoStack[++stackPtr].restore();
    
                    if (callback) {
                        callback();
                    }
                }
    
                mode = "none";
                panels.input.focus();
                refreshState();
            };
    
            // Push the input area state to the stack.
            var saveState = function () {
                var currState = inputStateObj || new TextareaState(panels);
    
                if (!currState) {
                    return false;
                }
                if (mode == "moving") {
                    if (!lastState) {
                        lastState = currState;
                    }
                    return;
                }
                if (lastState) {
                    if (undoStack[stackPtr - 1].text != lastState.text) {
                        undoStack[stackPtr++] = lastState;
                    }
                    lastState = null;
                }
                undoStack[stackPtr++] = currState;
                undoStack[stackPtr + 1] = null;
                if (callback) {
                    callback();
                }
            };
    
            var handleCtrlYZ = function (event) {
    
                var handled = false;
    
                if ((event.ctrlKey || event.metaKey) && !event.altKey) {
    
                    // IE and Opera do not support charCode.
                    var keyCode = event.charCode || event.keyCode;
                    var keyCodeChar = String.fromCharCode(keyCode);
    
                    switch (keyCodeChar.toLowerCase()) {
    
                        case "y":
                            undoObj.redo();
                            handled = true;
                            break;
    
                        case "z":
                            if (!event.shiftKey) {
                                undoObj.undo();
                            }
                            else {
                                undoObj.redo();
                            }
                            handled = true;
                            break;
                    }
                }
    
                if (handled) {
                    if (event.preventDefault) {
                        event.preventDefault();
                    }
                    if (window.event) {
                        window.event.returnValue = false;
                    }
                    return;
                }
            };
    
            // Set the mode depending on what is going on in the input area.
            var handleModeChange = function (event) {
    
                if (!event.ctrlKey && !event.metaKey) {
    
                    var keyCode = event.keyCode;
    
                    if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
                        // 33 - 40: page up/dn and arrow keys
                        // 63232 - 63235: page up/dn and arrow keys on safari
                        setMode("moving");
                    }
                    else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
                        // 8: backspace
                        // 46: delete
                        // 127: delete
                        setMode("deleting");
                    }
                    else if (keyCode == 13) {
                        // 13: Enter
                        setMode("newlines");
                    }
                    else if (keyCode == 27) {
                        // 27: escape
                        setMode("escape");
                    }
                    else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
                        // 16-20 are shift, etc.
                        // 91: left window key
                        // I think this might be a little messed up since there are
                        // a lot of nonprinting keys above 20.
                        setMode("typing");
                    }
                }
            };
    
            var setEventHandlers = function () {
                util.addEvent(panels.input, "keypress", function (event) {
                    // keyCode 89: y
                    // keyCode 90: z
                    if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) {
                        event.preventDefault();
                    }
                });
    
                var handlePaste = function () {
                    if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) {
                        if (timer == undefined) {
                            mode = "paste";
                            saveState();
                            refreshState();
                        }
                    }
                };
    
                //util.addEvent(panels.input, "keydown", handleCtrlYZ);
                util.addEvent(panels.input, "keydown", handleModeChange);
                util.addEvent(panels.input, "mousedown", function () {
                    setMode("moving");
                });
    
                panels.input.onpaste = handlePaste;
                panels.input.ondrop = handlePaste;
            };
    
            var init = function () {
                setEventHandlers();
                refreshState(true);
                //Not necessary
                //saveState();
            };
    
            this.reinit = function(content, start, end, scrollTop) {
                undoStack = [];
                stackPtr = 0;
                mode = "none";
                lastState = undefined;
                timer = undefined;
                refreshState();
                inputStateObj.text = content;
                inputStateObj.start = start;
                inputStateObj.end = end;
                inputStateObj.scrollTop = scrollTop;
                inputStateObj.setInputAreaSelection();
                saveState();
            };
            this.setMode = setMode;
    
            init();
        }
    
        // end of UndoManager
    
        // The input textarea state/contents.
        // This is used to implement undo/redo by the undo manager.
        function TextareaState(panels, isInitialState) {
    
            // Aliases
            var stateObj = this;
            var inputArea = panels.input;
            this.init = function () {
                if (!util.isVisible(inputArea)) {
                    return;
                }
    
                this.setInputAreaSelectionStartEnd();
                this.scrollTop = inputArea.scrollTop;
                if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
                    this.text = inputArea.value;
                }
    
            }
    
            // Sets the selected text in the input box after we've performed an
            // operation.
            this.setInputAreaSelection = function () {
    
                if (!util.isVisible(inputArea)) {
                    return;
                }
    
                //if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) {
    
                    inputArea.focus();
                    inputArea.selectionStart = stateObj.start;
                    inputArea.selectionEnd = stateObj.end;
    	        /*
                    inputArea.scrollTop = stateObj.scrollTop;
    
                }
                else if (doc.selection) {
    
                    inputArea.focus();
                    var range = inputArea.createTextRange();
                    range.moveStart("character", -inputArea.value.length);
                    range.moveEnd("character", -inputArea.value.length);
                    range.moveEnd("character", stateObj.end);
                    range.moveStart("character", stateObj.start);
                    range.select();
                }
                */
            };
    
            this.setInputAreaSelectionStartEnd = function () {
    
                //if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) {
    
                    stateObj.start = inputArea.selectionStart;
                    stateObj.end = inputArea.selectionEnd;
                    /*
                }
                else if (doc.selection) {
    
                    stateObj.text = util.fixEolChars(inputArea.value);
    
                    // IE loses the selection in the textarea when buttons are
                    // clicked.  On IE we cache the selection. Here, if something is cached,
                    // we take it.
                    var range = panels.ieCachedRange || doc.selection.createRange();
    
                    var fixedRange = util.fixEolChars(range.text);
                    var marker = "\x07";
                    var markedRange = marker + fixedRange + marker;
                    range.text = markedRange;
                    var inputText = util.fixEolChars(inputArea.value);
    
                    range.moveStart("character", -markedRange.length);
                    range.text = fixedRange;
    
                    stateObj.start = inputText.indexOf(marker);
                    stateObj.end = inputText.lastIndexOf(marker) - marker.length;
    
                    var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
    
                    if (len) {
                        range.moveStart("character", -fixedRange.length);
                        while (len--) {
                            fixedRange += "\n";
                            stateObj.end += 1;
                        }
                        range.text = fixedRange;
                    }
    
                    if (panels.ieCachedRange)
                        stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange
    
                    panels.ieCachedRange = null;
    
                    this.setInputAreaSelection();
                }
                */
            };
    
            // Restore this state into the input area.
            this.restore = function () {
    
                if (stateObj.text != undefined && stateObj.text != inputArea.value) {
                    inputArea.value = stateObj.text;
                }
                this.setInputAreaSelection();
    	        /*
                setTimeout(function() {
                    inputArea.scrollTop = stateObj.scrollTop;
                }, 0);
                */
            };
    
            // Gets a collection of HTML chunks from the inptut textarea.
            this.getChunks = function () {
    
                var chunk = new Chunks();
                chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
                chunk.startTag = "";
                chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
                chunk.endTag = "";
                chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
                chunk.scrollTop = stateObj.scrollTop;
    
                return chunk;
            };
    
            // Sets the TextareaState properties given a chunk of markdown.
            this.setChunks = function (chunk) {
    
                chunk.before = chunk.before + chunk.startTag;
                chunk.after = chunk.endTag + chunk.after;
    
                this.start = chunk.before.length;
                this.end = chunk.before.length + chunk.selection.length;
                this.text = chunk.before + chunk.selection + chunk.after;
                this.scrollTop = chunk.scrollTop;
            };
            this.init();
        };
    
        function PreviewManager(converter, panels, previewRefreshCallback) {
    
            var managerObj = this;
            var timeout;
            var elapsedTime;
            var oldInputText;
            var maxDelay = 3000;
            var startType = "manual"; // The other legal value is "manual"
    
            // Adds event listeners to elements
            var setupEvents = function (inputElem, listener) {
    
                util.addEvent(inputElem, "input", listener);
                inputElem.onpaste = listener;
                inputElem.ondrop = listener;
    
                util.addEvent(inputElem, "keypress", listener);
                util.addEvent(inputElem, "keydown", listener);
            };
    
            var getDocScrollTop = function () {
    
                var result = 0;
    
                if (window.innerHeight) {
                    result = window.pageYOffset;
                }
                else
                    if (doc.documentElement && doc.documentElement.scrollTop) {
                        result = doc.documentElement.scrollTop;
                    }
                    else
                        if (doc.body) {
                            result = doc.body.scrollTop;
                        }
    
                return result;
            };
    
            var makePreviewHtml = function () {
    
                // If there is no registered preview panel
                // there is nothing to do.
                if (!panels.preview)
                    return;
    
    
                var text = panels.input.value;
                if (text && text == oldInputText) {
                    return; // Input text hasn't changed.
                }
                else {
                    oldInputText = text;
                }
    
                var prevTime = new Date().getTime();
    
                text = converter.makeHtml(text);
    
                // Calculate the processing time of the HTML creation.
                // It's used as the delay time in the event listener.
                var currTime = new Date().getTime();
                elapsedTime = currTime - prevTime;
    
                pushPreviewHtml(text);
            };
    
            // setTimeout is already used.  Used as an event listener.
            var applyTimeout = function () {
    
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = undefined;
                }
    
                if (startType !== "manual") {
    
                    var delay = 0;
    
                    if (startType === "delayed") {
                        delay = elapsedTime;
                    }
    
                    if (delay > maxDelay) {
                        delay = maxDelay;
                    }
                    timeout = setTimeout(makePreviewHtml, delay);
                }
            };
    
            var getScaleFactor = function (panel) {
                if (panel.scrollHeight <= panel.clientHeight) {
                    return 1;
                }
                return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
            };
    
            var setPanelScrollTops = function () {
                if (panels.preview) {
                    panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview);
                }
            };
    
            this.refresh = function (requiresRefresh) {
    
                if (requiresRefresh) {
                    oldInputText = "";
                    makePreviewHtml();
                }
                else {
                    applyTimeout();
                }
            };
    
            this.processingTime = function () {
                return elapsedTime;
            };
    
            var isFirstTimeFilled = true;
    
            // IE doesn't let you use innerHTML if the element is contained somewhere in a table
            // (which is the case for inline editing) -- in that case, detach the element, set the
            // value, and reattach. Yes, that *is* ridiculous.
            var ieSafePreviewSet = function (text) {
                var preview = panels.preview;
                var parent = preview.parentNode;
                var sibling = preview.nextSibling;
                parent.removeChild(preview);
                preview.innerHTML = text;
                if (!sibling)
                    parent.appendChild(preview);
                else
                    parent.insertBefore(preview, sibling);
            }
    
            var nonSuckyBrowserPreviewSet = function (text) {
                panels.preview.innerHTML = text;
            }
    
            var previewSetter;
    
            var previewSet = function (text) {
                if (previewSetter)
                    return previewSetter(text);
    
                try {
                    nonSuckyBrowserPreviewSet(text);
                    previewSetter = nonSuckyBrowserPreviewSet;
                } catch (e) {
                    previewSetter = ieSafePreviewSet;
                    previewSetter(text);
                }
            };
    
            var pushPreviewHtml = function (text) {
    
                //var emptyTop = position.getTop(panels.input) - getDocScrollTop();
    
                if (panels.preview) {
                    previewSet(text);
                    previewRefreshCallback();
                }
                /*
    
                setPanelScrollTops();
    
                if (isFirstTimeFilled) {
                    isFirstTimeFilled = false;
                    return;
                }
    
                var fullTop = position.getTop(panels.input) - getDocScrollTop();
    
                if (uaSniffed.isIE) {
                    setTimeout(function () {
                        window.scrollBy(0, fullTop - emptyTop);
                    }, 0);
                }
                else {
                    window.scrollBy(0, fullTop - emptyTop);
                }
                */
            };
    
            var init = function () {
    
                setupEvents(panels.input, applyTimeout);
                //Not necessary
                //makePreviewHtml();
    
                if (panels.preview) {
                    panels.preview.scrollTop = 0;
                }
            };
    
            init();
        };
    
        // Creates the background behind the hyperlink text entry box.
        // And download dialog
        // Most of this has been moved to CSS but the div creation and
        // browser-specific hacks remain here.
        ui.createBackground = function () {
    
            var background = doc.createElement("div"),
                style = background.style;
    
            background.className = "wmd-prompt-background";
    
            style.position = "absolute";
            style.top = "0";
    
            style.zIndex = "1000";
    
            if (uaSniffed.isIE) {
                style.filter = "alpha(opacity=50)";
            }
            else {
                style.opacity = "0.5";
            }
    
            var pageSize = position.getPageSize();
            style.height = pageSize[1] + "px";
    
            if (uaSniffed.isIE) {
                style.left = doc.documentElement.scrollLeft;
                style.width = doc.documentElement.clientWidth;
            }
            else {
                style.left = "0";
                style.width = "100%";
            }
    
            doc.body.appendChild(background);
            return background;
        };
    
        // This simulates a modal dialog box and asks for the URL when you
        // click the hyperlink or image buttons.
        //
        // text: The html for the input box.
        // defaultInputText: The default value that appears in the input box.
        // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel.
        //      It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
        //      was chosen).
        ui.prompt = function (text, defaultInputText, callback) {
    
            // These variables need to be declared at this level since they are used
            // in multiple functions.
            var dialog;         // The dialog box.
            var input;         // The text box where you enter the hyperlink.
    
    
            if (defaultInputText === undefined) {
                defaultInputText = "";
            }
    
            // Used as a keydown event handler. Esc dismisses the prompt.
            // Key code 27 is ESC.
            var checkEscape = function (key) {
                var code = (key.charCode || key.keyCode);
                if (code === 27) {
                    close(true);
                }
            };
    
            // Dismisses the hyperlink input box.
            // isCancel is true if we don't care about the input text.
            // isCancel is false if we are going to keep the text.
            var close = function (isCancel) {
                util.removeEvent(doc.body, "keydown", checkEscape);
                var text = input.value;
    
                if (isCancel) {
                    text = null;
                }
                else {
                    // Fixes common pasting errors.
                    text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://');
                    if (!/^(?:https?|ftp):\/\//.test(text))
                        text = 'http://' + text;
                }
    
                dialog.parentNode.removeChild(dialog);
    
                callback(text);
                return false;
            };
    
    
    
            // Create the text input box form/window.
            var createDialog = function () {
    
                // The main dialog box.
                dialog = doc.createElement("div");
                dialog.className = "wmd-prompt-dialog";
                dialog.style.padding = "10px;";
                dialog.style.position = "fixed";
                dialog.style.width = "400px";
                dialog.style.zIndex = "1001";
    
                // The dialog text.
                var question = doc.createElement("div");
                question.innerHTML = text;
                question.style.padding = "5px";
                dialog.appendChild(question);
    
                // The web form container for the text box and buttons.
                var form = doc.createElement("form"),
                    style = form.style;
                form.onsubmit = function () { return close(false); };
                style.padding = "0";
                style.margin = "0";
                style.cssFloat = "left";
                style.width = "100%";
                style.textAlign = "center";
                style.position = "relative";
                dialog.appendChild(form);
    
                // The input text box
                input = doc.createElement("input");
                input.type = "text";
                input.value = defaultInputText;
                style = input.style;
                style.display = "block";
                style.width = "80%";
                style.marginLeft = style.marginRight = "auto";
                form.appendChild(input);
    
                // The ok button
                var okButton = doc.createElement("input");
                okButton.type = "button";
                okButton.onclick = function () { return close(false); };
                okButton.value = "OK";
                style = okButton.style;
                style.margin = "10px";
                style.display = "inline";
                style.width = "7em";
    
    
                // The cancel button
                var cancelButton = doc.createElement("input");
                cancelButton.type = "button";
                cancelButton.onclick = function () { return close(true); };
                cancelButton.value = "Cancel";
                style = cancelButton.style;
                style.margin = "10px";
                style.display = "inline";
                style.width = "7em";
    
                form.appendChild(okButton);
                form.appendChild(cancelButton);
    
                util.addEvent(doc.body, "keydown", checkEscape);
                dialog.style.top = "50%";
                dialog.style.left = "50%";
                dialog.style.display = "block";
                if (uaSniffed.isIE_5or6) {
                    dialog.style.position = "absolute";
                    dialog.style.top = doc.documentElement.scrollTop + 200 + "px";
                    dialog.style.left = "50%";
                }
                doc.body.appendChild(dialog);
    
                // This has to be done AFTER adding the dialog to the form if you
                // want it to be centered.
                dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
                dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
    
            };
    
            // Why is this in a zero-length timeout?
            // Is it working around a browser bug?
            setTimeout(function () {
    
                createDialog();
    
                var defTextLen = defaultInputText.length;
                if (input.selectionStart !== undefined) {
                    input.selectionStart = 0;
                    input.selectionEnd = defTextLen;
                }
                else if (input.createTextRange) {
                    var range = input.createTextRange();
                    range.collapse(false);
                    range.moveStart("character", -defTextLen);
                    range.moveEnd("character", defTextLen);
                    range.select();
                }
    
                input.focus();
            }, 0);
        };
    
        function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, getString) {
    
            var inputBox = panels.input,
                buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements.
    
            makeSpritedButtonRow();
    
            var keyEvent = "keydown";
            if (uaSniffed.isOpera) {
                keyEvent = "keypress";
            }
    
            /*
            util.addEvent(inputBox, keyEvent, function (key) {
    
                // Check to see if we have a button key and, if so execute the callback.
                if ((key.ctrlKey || key.metaKey) && !key.altKey) {
    
                    var keyCode = key.charCode || key.keyCode;
                    var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
    
                    switch (keyCodeStr) {
                        case "b":
                            doClick(buttons.bold);
                            break;
                        case "i":
                            doClick(buttons.italic);
                            break;
                        case "l":
                            doClick(buttons.link);
                            break;
                        case "q":
                            doClick(buttons.quote);
                            break;
                        case "k":
                            doClick(buttons.code);
                            break;
                        case "g":
                            doClick(buttons.image);
                            break;
                        case "o":
                            doClick(buttons.olist);
                            break;
                        case "u":
                            doClick(buttons.ulist);
                            break;
                        case "h":
                            doClick(buttons.heading);
                            break;
                        case "r":
                            doClick(buttons.hr);
                            break;
                        case "y":
                            doClick(buttons.redo);
                            break;
                        case "z":
                            if (key.shiftKey) {
                                doClick(buttons.redo);
                            }
                            else {
                                doClick(buttons.undo);
                            }
                            break;
                        case "v":
                            undoManager.setMode("typing");
                            return;
                        case "x":
                            undoManager.setMode("deleting");
                            return;
                        default:
                            return;
                    }
    
    
                    if (key.preventDefault) {
                        key.preventDefault();
                    }
    
                    if (window.event) {
                        window.event.returnValue = false;
                    }
                }
            });
    
            // Auto-indent on shift-enter
            util.addEvent(inputBox, "keyup", function (key) {
                if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
                    var keyCode = key.charCode || key.keyCode;
                    // Character 13 is Enter
                    if (keyCode === 13) {
                        var fakeButton = {};
                        fakeButton.textOp = bindCommand("doAutoindent");
                        doClick(fakeButton);
                    }
                }
            });
    
            // special handler because IE clears the context of the textbox on ESC
            if (uaSniffed.isIE) {
                util.addEvent(inputBox, "keydown", function (key) {
                    var code = key.keyCode;
                    if (code === 27) {
                        return false;
                    }
                });
            }
            */
           
            // life 新添加函数
            // life
            // isImage 2015/3/1
            function insertLinkLife(link, text, isImage) {
                inputBox.focus();
                if (undoManager) {
                    undoManager.setCommandMode();
                }
    
                var state = new TextareaState(panels);
    
                if (!state) {
                    return;
                }
    
                var chunks = state.getChunks(); // 得到chunk
                var fixupInputArea = function () {
                    inputBox.focus();
    
                    if (chunks) {
                        state.setChunks(chunks);
                    }
    
                    state.restore();
                    previewManager.refresh();
                };
    
                var a = commandProto.insertLink(chunks, fixupInputArea, link, text, isImage);
                if(!a) fixupInputArea();
            }
           
            // life
            MD.insertLink = insertLinkLife;
    
            // Perform the button's action.
            function doClick(button) {
    
                inputBox.focus();
                var linkOrImage = button.id == "wmd-link-button" || button.id == "wmd-image-button";
    
                if (button.textOp) {
    
                    if (undoManager && !linkOrImage) {
                        undoManager.setCommandMode();
                    }
    
                    var state = new TextareaState(panels);
    
                    if (!state) {
                        return;
                    }
    
                    var chunks = state.getChunks();
    
                    // Some commands launch a "modal" prompt dialog.  Javascript
                    // can't really make a modal dialog box and the WMD code
                    // will continue to execute while the dialog is displayed.
                    // This prevents the dialog pattern I'm used to and means
                    // I can't do something like this:
                    //
                    // var link = CreateLinkDialog();
                    // makeMarkdownLink(link);
                    //
                    // Instead of this straightforward method of handling a
                    // dialog I have to pass any code which would execute
                    // after the dialog is dismissed (e.g. link creation)
                    // in a function parameter.
                    //
                    // Yes this is awkward and I think it sucks, but there's
                    // no real workaround.  Only the image and link code
                    // create dialogs and require the function pointers.
                    var fixupInputArea = function () {
    
                        inputBox.focus();
    
                        if (chunks) {
                            state.setChunks(chunks);
                        }
    
                        state.restore();
                        previewManager.refresh();
                    };
    
                    var noCleanup = button.textOp(chunks, fixupInputArea);
    
                    if (!noCleanup) {
                        fixupInputArea();
                        if(!linkOrImage) {
    	                    inputBox.adjustCursorPosition();
    	                    //inputBox.dispatchEvent(new Event('keydown'));
                        }
                    }
    
                }
    
                if (button.execute) {
                    button.execute(undoManager);
                }
            };
    
            function setupButton(button, isEnabled) {
    
                var normalYShift = "0px";
                var disabledYShift = "-20px";
                var highlightYShift = "-40px";
                var image = button.getElementsByTagName("span")[0];
                button.className = button.className.replace(/ disabled/g, "");
                if (isEnabled) {
                    image.style.backgroundPosition = button.XShift + " " + normalYShift;
                    button.onmouseover = function () {
                        image.style.backgroundPosition = this.XShift + " " + highlightYShift;
                    };
    
                    button.onmouseout = function () {
                        image.style.backgroundPosition = this.XShift + " " + normalYShift;
                    };
    
                    // IE tries to select the background image "button" text (it's
                    // implemented in a list item) so we have to cache the selection
                    // on mousedown.
                    if (uaSniffed.isIE) {
                        button.onmousedown = function () {
                            panels.ieCachedRange = document.selection.createRange();
                            panels.ieCachedScrollTop = panels.input.scrollTop;
                        };
                    }
    
                    if (!button.isHelp) {
                        button.onclick = function () {
                            if (this.onmouseout) {
                                this.onmouseout();
                            }
                            doClick(this);
                            return false;
                        }
                    }
                }
                else {
                    image.style.backgroundPosition = button.XShift + " " + disabledYShift;
                    button.onmouseover = button.onmouseout = button.onclick = function () { };
                    button.className += " disabled";
                }
            }
    
            function bindCommand(method) {
                if (typeof method === "string")
                    method = commandManager[method];
                return function () { method.apply(commandManager, arguments); }
            }
    
            function makeSpritedButtonRow() {
    
                var buttonBar = panels.buttonBar;
    
                var normalYShift = "0px";
                var disabledYShift = "-20px";
                var highlightYShift = "-40px";
    
                var buttonRow = document.createElement("ul");
                buttonRow.id = "wmd-button-row" + postfix;
                buttonRow.className = 'wmd-button-row';
                buttonRow = buttonBar.appendChild(buttonRow);
                var xPosition = 0;
                var makeButton = function (id, title, XShift, textOp) {
                    var button = document.createElement("li");
                    button.className = "wmd-button";
                    button.style.left = xPosition + "px";
                    xPosition += 25;
                    var buttonImage = document.createElement("span");
                    button.id = id + postfix;
                    button.appendChild(buttonImage);
                    button.title = title;
                    button.XShift = XShift;
                    if (textOp)
                        button.textOp = textOp;
                    setupButton(button, true);
                    buttonRow.appendChild(button);
                    return button;
                };
                var makeSpacer = function (num) {
                    var spacer = document.createElement("li");
                    spacer.className = "wmd-spacer wmd-spacer" + num;
                    spacer.id = "wmd-spacer" + num + postfix;
                    buttonRow.appendChild(spacer);
                    xPosition += 25;
                }
    
                buttons.bold = makeButton("wmd-bold-button", getString("bold"), "0px", bindCommand("doBold"));
                buttons.italic = makeButton("wmd-italic-button", getString("italic"), "-20px", bindCommand("doItalic"));
                makeSpacer(1);
                buttons.link = makeButton("wmd-link-button", getString("link"), "-40px", bindCommand(function (chunk, postProcessing) {
                    return this.doLinkOrImage(chunk, postProcessing, false);
                }));
                buttons.quote = makeButton("wmd-quote-button", getString("quote"), "-60px", bindCommand("doBlockquote"));
                buttons.code = makeButton("wmd-code-button", getString("code"), "-80px", bindCommand("doCode"));
                buttons.image = makeButton("wmd-image-button", getString("image"), "-100px", bindCommand(function (chunk, postProcessing) {
                    return this.doLinkOrImage(chunk, postProcessing, true);
                }));
                makeSpacer(2);
                buttons.olist = makeButton("wmd-olist-button", getString("olist"), "-120px", bindCommand(function (chunk, postProcessing) {
                    this.doList(chunk, postProcessing, true);
                }));
                buttons.ulist = makeButton("wmd-ulist-button", getString("ulist"), "-140px", bindCommand(function (chunk, postProcessing) {
                    this.doList(chunk, postProcessing, false);
                }));
                buttons.heading = makeButton("wmd-heading-button", getString("heading"), "-160px", bindCommand("doHeading"));
                buttons.hr = makeButton("wmd-hr-button", getString("hr"), "-180px", bindCommand("doHorizontalRule"));
                makeSpacer(3);
                buttons.undo = makeButton("wmd-undo-button", getString("undo"), "-200px", null);
                buttons.undo.execute = function (manager) { if (manager) manager.undo(); };
    
                buttons.redo = makeButton("wmd-redo-button", getString("redo"), "-220px", null);
                buttons.redo.execute = function (manager) { if (manager) manager.redo(); };
    
                if (helpOptions) {
                    var helpButton = document.createElement("li");
                    var helpButtonImage = document.createElement("span");
                    helpButton.appendChild(helpButtonImage);
                    helpButton.className = "wmd-button wmd-help-button";
                    helpButton.id = "wmd-help-button" + postfix;
                    helpButton.XShift = "-240px";
                    helpButton.isHelp = true;
                    helpButton.style.right = "0px";
                    helpButton.title = getString("help");
                    helpButton.onclick = helpOptions.handler;
    
                    setupButton(helpButton, true);
                    buttonRow.appendChild(helpButton);
                    buttons.help = helpButton;
                }
    
                setUndoRedoButtonStates();
            }
    
            function setUndoRedoButtonStates() {
                if (undoManager) {
                    setupButton(buttons.undo, undoManager.canUndo());
                    setupButton(buttons.redo, undoManager.canRedo());
                }
            };
    
            this.setUndoRedoButtonStates = setUndoRedoButtonStates;
            this.buttons = buttons;
            this.doClick = doClick;
    
        }
    
        function CommandManager(pluginHooks, getString) {
            this.hooks = pluginHooks;
            this.getString = getString;
        }
    
        var commandProto = CommandManager.prototype;
    
        // The markdown symbols - 4 spaces = code, > = blockquote, etc.
        commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
    
        // Remove markdown symbols from the chunk selection.
        commandProto.unwrap = function (chunk) {
            var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g");
            chunk.selection = chunk.selection.replace(txt, "$1 $2");
        };
    
        commandProto.wrap = function (chunk, len) {
            this.unwrap(chunk);
            var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"),
                that = this;
    
            chunk.selection = chunk.selection.replace(regex, function (line, marked) {
                if (new re("^" + that.prefixes, "").test(line)) {
                    return line;
                }
                return marked + "\n";
            });
    
            chunk.selection = chunk.selection.replace(/\s+$/, "");
        };
    
        commandProto.doBold = function (chunk, postProcessing) {
            return this.doBorI(chunk, postProcessing, 2, this.getString("boldexample"));
        };
    
        commandProto.doItalic = function (chunk, postProcessing) {
            return this.doBorI(chunk, postProcessing, 1, this.getString("italicexample"));
        };
    
        // chunk: The selected region that will be enclosed with */**
        // nStars: 1 for italics, 2 for bold
        // insertText: If you just click the button without highlighting text, this gets inserted
        commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) {
    
            // Get rid of whitespace and fixup newlines.
            chunk.trimWhitespace();
            chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
    
            // Look for stars before and after.  Is the chunk already marked up?
            // note that these regex matches cannot fail
            var starsBefore = /(\**$)/.exec(chunk.before)[0];
            var starsAfter = /(^\**)/.exec(chunk.after)[0];
    
            var prevStars = Math.min(starsBefore.length, starsAfter.length);
    
            // Remove stars if we have to since the button acts as a toggle.
            if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
                chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
                chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
            }
            else if (!chunk.selection && starsAfter) {
                // It's not really clear why this code is necessary.  It just moves
                // some arbitrary stuff around.
                chunk.after = chunk.after.replace(/^([*_]*)/, "");
                chunk.before = chunk.before.replace(/(\s?)$/, "");
                var whitespace = re.$1;
                chunk.before = chunk.before + starsAfter + whitespace;
            }
            else {
    
                // In most cases, if you don't have any selected text and click the button
                // you'll get a selected, marked up region with the default text inserted.
                if (!chunk.selection && !starsAfter) {
                    chunk.selection = insertText;
                }
    
                // Add the true markup.
                var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
                chunk.before = chunk.before + markup;
                chunk.after = markup + chunk.after;
            }
    
            return;
        };
    
        commandProto.stripLinkDefs = function (text, defsToAdd) {
    
            text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
                function (totalMatch, id, link, newlines, title) {
                    defsToAdd[id] = totalMatch.replace(/\s*$/, "");
                    if (newlines) {
                        // Strip the title and return that separately.
                        defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
                        return newlines + title;
                    }
                    return "";
                });
    
            return text;
        };
    
        commandProto.addLinkDef = function (chunk, linkDef) {
    
            var refNumber = 0; // The current reference number
            var defsToAdd = {}; //
            // Start with a clean slate by removing all previous link definitions.
            chunk.before = this.stripLinkDefs(chunk.before, defsToAdd);
            chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd);
            chunk.after = this.stripLinkDefs(chunk.after, defsToAdd);
    
            var defs = "";
            var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
    
            var addDefNumber = function (def) {
                refNumber++;
                def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, "  [" + refNumber + "]:");
                defs += "\n" + def;
            };
    
            // note that
            // a) the recursive call to getLink cannot go infinite, because by definition
            //    of regex, inner is always a proper substring of wholeMatch, and
            // b) more than one level of nesting is neither supported by the regex
            //    nor making a lot of sense (the only use case for nesting is a linked image)
            var getLink = function (wholeMatch, before, inner, afterInner, id, end) {
                inner = inner.replace(regex, getLink);
                if (defsToAdd[id]) {
                    addDefNumber(defsToAdd[id]);
                    return before + inner + afterInner + refNumber + end;
                }
                return wholeMatch;
            };
    
            chunk.before = chunk.before.replace(regex, getLink);
    
            if (linkDef) {
                addDefNumber(linkDef);
            }
            else {
                chunk.selection = chunk.selection.replace(regex, getLink);
            }
    
            var refOut = refNumber;
    
            chunk.after = chunk.after.replace(regex, getLink);
    
            if (chunk.after) {
                chunk.after = chunk.after.replace(/\n*$/, "");
            }
            if (!chunk.after) {
                chunk.selection = chunk.selection.replace(/\n*$/, "");
            }
    
            chunk.after += "\n\n" + defs;
    
            return refOut;
        };
    
        // takes the line as entered into the add link/as image dialog and makes
        // sure the URL and the optinal title are "nice".
        function properlyEncoded(linkdef) {
            return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) {
                link = link.replace(/\?.*$/, function (querypart) {
                    return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical
                });
                link = decodeURIComponent(link); // unencode first, to prevent double encoding
                link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
                link = link.replace(/\?.*$/, function (querypart) {
                    return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded
                });
                if (title) {
                    title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, "");
                    title = title.replace(/"/g, "quot;").replace(/\(/g, "&#40;").replace(/\)/g, "&#41;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                }
                return title ? link + ' "' + title + '"' : link;
            });
        }
    
         // life 添加
        commandProto.insertLink = function (chunk, postProcessing, link, text, isImage) {
            chunk.trimWhitespace();
            chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
            var background;
    
            if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
    
                chunk.startTag = chunk.startTag.replace(/!?\[/, "");
                chunk.endTag = "";
                this.addLinkDef(chunk, null);
            }
            else {
                
                // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
                // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
                // link text. linkEnteredCallback takes care of escaping any brackets.
                chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
                chunk.startTag = chunk.endTag = "";
    
                if (/\n\n/.test(chunk.selection)) {
                    this.addLinkDef(chunk, null);
                    return;
                }
                var that = this;
                // The function to be executed when you enter a link and press OK or Cancel.
                // Marks up the link and adds the ref.
                var linkEnteredCallback = function (link) {
                    background.parentNode.removeChild(background);
    
                    if (link !== null) {
                        chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
    
                        chunk.startTag = isImage ? "![" : "[";
                        //chunk.endTag = "][" + num + "]";
                        chunk.endTag = "](" + properlyEncoded(link) + ")";
    
                        chunk.selection = text;
                    }
                    postProcessing();
                };
    
                background = ui.createBackground();
                linkEnteredCallback(link);
                return true;
            }
        };
    
        commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
    
            chunk.trimWhitespace();
            //chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
            chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\(.*?\))?/);
    
            var background;
    
            if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
    
                chunk.startTag = chunk.startTag.replace(/!?\[/, "");
                chunk.endTag = "";
                this.addLinkDef(chunk, null);
    
            }
            else {
    
                // We're moving start and end tag back into the selection, since (as we're in the else block) we're not
                // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
                // link text. linkEnteredCallback takes care of escaping any brackets.
                chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
                chunk.startTag = chunk.endTag = "";
    
                if (/\n\n/.test(chunk.selection)) {
                    this.addLinkDef(chunk, null);
                    return;
                }
                var that = this;
                // The function to be executed when you enter a link and press OK or Cancel.
                // Marks up the link and adds the ref.
                var linkEnteredCallback = function (link) {
    
                    background.parentNode.removeChild(background);
    
                    if (link !== null) {
                        // (                          $1
                        //     [^\\]                  anything that's not a backslash
                        //     (?:\\\\)*              an even number (this includes zero) of backslashes
                        // )
                        // (?=                        followed by
                        //     [[\]]                  an opening or closing bracket
                        // )
                        //
                        // In other words, a non-escaped bracket. These have to be escaped now to make sure they
                        // don't count as the end of the link or similar.
                        // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets),
                        // the bracket in one match may be the "not a backslash" character in the next match, so it
                        // should not be consumed by the first match.
                        // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the
                        // start of the string, so this also works if the selection begins with a bracket. We cannot solve
                        // this by anchoring with ^, because in the case that the selection starts with two brackets, this
                        // would mean a zero-width match at the start. Since zero-width matches advance the string position,
                        // the first bracket could then not act as the "not a backslash" for the second.
                        chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
    
                        /*
                        var linkDef = " [999]: " + properlyEncoded(link);
    
                        var num = that.addLinkDef(chunk, linkDef);
                        */
                        chunk.startTag = isImage ? "![" : "[";
                        //chunk.endTag = "][" + num + "]";
                        chunk.endTag = "](" + properlyEncoded(link) + ")";
    
                        if (!chunk.selection) {
                            if (isImage) {
                                chunk.selection = that.getString("imagedescription");
                            }
                            else {
                                chunk.selection = that.getString("linkdescription");
                            }
                        }
                    }
                    postProcessing();
                };
    
    
                background = ui.createBackground();
    
                if (isImage) {
                    if (!this.hooks.insertImageDialog(linkEnteredCallback)) {
                        // ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback);
                    }
                }
                else {
                    if (!this.hooks.insertLinkDialog(linkEnteredCallback))
                    	ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback);
                }
                return true;
            }
        };
    
        // When making a list, hitting shift-enter will put your cursor on the next line
        // at the current indent level.
        commandProto.doAutoindent = function (chunk, postProcessing) {
    
            var commandMgr = this,
                fakeSelection = false;
    
            chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
            chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
            chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
    
            // There's no selection, end the cursor wasn't at the end of the line:
            // The user wants to split the current list item / code line / blockquote line
            // (for the latter it doesn't really matter) in two. Temporarily select the
            // (rest of the) line to achieve this.
            if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) {
                chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) {
                    chunk.selection = wholeMatch;
                    return "";
                });
                fakeSelection = true;
            }
    
            if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
                if (commandMgr.doList) {
                    commandMgr.doList(chunk);
                }
            }
            if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
                if (commandMgr.doBlockquote) {
                    commandMgr.doBlockquote(chunk);
                }
            }
            if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
                if (commandMgr.doCode) {
                    commandMgr.doCode(chunk);
                }
            }
    
            if (fakeSelection) {
                chunk.after = chunk.selection + chunk.after;
                chunk.selection = "";
            }
        };
    
        commandProto.doBlockquote = function (chunk, postProcessing) {
    
            chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
                function (totalMatch, newlinesBefore, text, newlinesAfter) {
                    chunk.before += newlinesBefore;
                    chunk.after = newlinesAfter + chunk.after;
                    return text;
                });
    
            chunk.before = chunk.before.replace(/(>[ \t]*)$/,
                function (totalMatch, blankLine) {
                    chunk.selection = blankLine + chunk.selection;
                    return "";
                });
    
            chunk.selection = chunk.selection.replace(/^(\s|>)+$/, "");
            chunk.selection = chunk.selection || this.getString("quoteexample");
    
            // The original code uses a regular expression to find out how much of the
            // text *directly before* the selection already was a blockquote:
    
            /*
            if (chunk.before) {
            chunk.before = chunk.before.replace(/\n?$/, "\n");
            }
            chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
            function (totalMatch) {
            chunk.startTag = totalMatch;
            return "";
            });
            */
    
            // This comes down to:
            // Go backwards as many lines a possible, such that each line
            //  a) starts with ">", or
            //  b) is almost empty, except for whitespace, or
            //  c) is preceeded by an unbroken chain of non-empty lines
            //     leading up to a line that starts with ">" and at least one more character
            // and in addition
            //  d) at least one line fulfills a)
            //
            // Since this is essentially a backwards-moving regex, it's susceptible to
            // catstrophic backtracking and can cause the browser to hang;
            // see e.g. http://meta.stackoverflow.com/questions/9807.
            //
            // Hence we replaced this by a simple state machine that just goes through the
            // lines and checks for a), b), and c).
    
            var match = "",
                leftOver = "",
                line;
            if (chunk.before) {
                var lines = chunk.before.replace(/\n$/, "").split("\n");
                var inChain = false;
                for (var i = 0; i < lines.length; i++) {
                    var good = false;
                    line = lines[i];
                    inChain = inChain && line.length > 0; // c) any non-empty line continues the chain
                    if (/^>/.test(line)) {                // a)
                        good = true;
                        if (!inChain && line.length > 1)  // c) any line that starts with ">" and has at least one more character starts the chain
                            inChain = true;
                    } else if (/^[ \t]*$/.test(line)) {   // b)
                        good = true;
                    } else {
                        good = inChain;                   // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain
                    }
                    if (good) {
                        match += line + "\n";
                    } else {
                        leftOver += match + line;
                        match = "\n";
                    }
                }
                if (!/(^|\n)>/.test(match)) {             // d)
                    leftOver += match;
                    match = "";
                }
            }
    
            chunk.startTag = match;
            chunk.before = leftOver;
    
            // end of change
    
            if (chunk.after) {
                chunk.after = chunk.after.replace(/^\n?/, "\n");
            }
    
            chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
                function (totalMatch) {
                    chunk.endTag = totalMatch;
                    return "";
                }
            );
    
            var replaceBlanksInTags = function (useBracket) {
    
                var replacement = useBracket ? "> " : "";
    
                if (chunk.startTag) {
                    chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
                        function (totalMatch, markdown) {
                            return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
                        });
                }
                if (chunk.endTag) {
                    chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
                        function (totalMatch, markdown) {
                            return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
                        });
                }
            };
    
            if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
                this.wrap(chunk, SETTINGS.lineLength - 2);
                chunk.selection = chunk.selection.replace(/^/gm, "> ");
                replaceBlanksInTags(true);
                chunk.skipLines();
            } else {
                chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
                this.unwrap(chunk);
                replaceBlanksInTags(false);
    
                if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
                    chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
                }
    
                if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
                    chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
                }
            }
    
            chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection);
    
            if (!/\n/.test(chunk.selection)) {
                chunk.selection = chunk.selection.replace(/^(> *)/,
                function (wholeMatch, blanks) {
                    chunk.startTag += blanks;
                    return "";
                });
            }
        };
    
        commandProto.doCode = function (chunk, postProcessing) {
    
            var hasTextBefore = /\S[ ]*$/.test(chunk.before);
            var hasTextAfter = /^[ ]*\S/.test(chunk.after);
    
            // Use 'four space' markdown if the selection is on its own
            // line or is multiline.
            if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
    
                chunk.before = chunk.before.replace(/[ ]{4}$/,
                    function (totalMatch) {
                        chunk.selection = totalMatch + chunk.selection;
                        return "";
                    });
    
                var nLinesBack = 1;
                var nLinesForward = 1;
    
                if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
                    nLinesBack = 0;
                }
                if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
                    nLinesForward = 0;
                }
    
                chunk.skipLines(nLinesBack, nLinesForward);
    
                if (!chunk.selection) {
                    chunk.startTag = "    ";
                    chunk.selection = this.getString("codeexample");
                }
                else {
                    if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
                        if (/\n/.test(chunk.selection))
                            chunk.selection = chunk.selection.replace(/^/gm, "    ");
                        else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
                            chunk.before += "    ";
                    }
                    else {
                        chunk.selection = chunk.selection.replace(/^(?:[ ]{4}|[ ]{0,3}\t)/gm, "");
                    }
                }
            }
            else {
                // Use backticks (`) to delimit the code block.
    
                chunk.trimWhitespace();
                chunk.findTags(/`/, /`/);
    
                if (!chunk.startTag && !chunk.endTag) {
                    chunk.startTag = chunk.endTag = "`";
                    if (!chunk.selection) {
                        chunk.selection = this.getString("codeexample");
                    }
                }
                else if (chunk.endTag && !chunk.startTag) {
                    chunk.before += chunk.endTag;
                    chunk.endTag = "";
                }
                else {
                    chunk.startTag = chunk.endTag = "";
                }
            }
        };
    
        commandProto.doList = function (chunk, postProcessing, isNumberedList) {
    
            // These are identical except at the very beginning and end.
            // Should probably use the regex extension function to make this clearer.
            var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
            var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
    
            // The default bullet is a dash but others are possible.
            // This has nothing to do with the particular HTML bullet,
            // it's just a markdown bullet.
            var bullet = "-";
    
            // The number in a numbered list.
            var num = 1;
    
            // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
            var getItemPrefix = function () {
                var prefix;
                if (isNumberedList) {
                    prefix = " " + num + ". ";
                    num++;
                }
                else {
                    prefix = " " + bullet + " ";
                }
                return prefix;
            };
    
            // Fixes the prefixes of the other list items.
            var getPrefixedItem = function (itemText) {
    
                // The numbering flag is unset when called by autoindent.
                if (isNumberedList === undefined) {
                    isNumberedList = /^\s*\d/.test(itemText);
                }
    
                // Renumber/bullet the list element.
                itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
                    function (_) {
                        return getItemPrefix();
                    });
    
                return itemText;
            };
    
            chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
    
            if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
                chunk.before += chunk.startTag;
                chunk.startTag = "";
            }
    
            if (chunk.startTag) {
    
                var hasDigits = /\d+[.]/.test(chunk.startTag);
                chunk.startTag = "";
                chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
                this.unwrap(chunk);
                chunk.skipLines();
    
                if (hasDigits) {
                    // Have to renumber the bullet points if this is a numbered list.
                    chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
                }
                if (isNumberedList == hasDigits) {
                    return;
                }
            }
    
            var nLinesUp = 1;
    
            chunk.before = chunk.before.replace(previousItemsRegex,
                function (itemText) {
                    if (/^\s*([*+-])/.test(itemText)) {
                        bullet = re.$1;
                    }
                    nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                    return getPrefixedItem(itemText);
                });
    
            if (!chunk.selection) {
                chunk.selection = this.getString("litem");
            }
    
            var prefix = getItemPrefix();
    
            var nLinesDown = 1;
    
            chunk.after = chunk.after.replace(nextItemsRegex,
                function (itemText) {
                    nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                    return getPrefixedItem(itemText);
                });
    
            chunk.trimWhitespace(true);
            chunk.skipLines(nLinesUp, nLinesDown, true);
            chunk.startTag = prefix;
            var spaces = prefix.replace(/./g, " ");
            this.wrap(chunk, SETTINGS.lineLength - spaces.length);
            chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
    
        };
    
        commandProto.doHeading = function (chunk, postProcessing) {
    
            // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
            chunk.selection = chunk.selection.replace(/\s+/g, " ");
            chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
    
            // If we clicked the button with no selected text, we just
            // make a level 2 hash header around some default text.
            if (!chunk.selection) {
                chunk.startTag = "## ";
                chunk.selection = this.getString("headingexample");
                chunk.endTag = " ##";
                return;
            }
    
            var headerLevel = 0;     // The existing header level of the selected text.
    
            // Remove any existing hash heading markdown and save the header level.
            chunk.findTags(/#+[ ]*/, /[ ]*#+/);
            if (/#+/.test(chunk.startTag)) {
                headerLevel = re.lastMatch.length;
            }
            chunk.startTag = chunk.endTag = "";
    
            // Try to get the current header level by looking for - and = in the line
            // below the selection.
            chunk.findTags(null, /\s?(-+|=+)/);
            if (/=+/.test(chunk.endTag)) {
                headerLevel = 1;
            }
            if (/-+/.test(chunk.endTag)) {
                headerLevel = 2;
            }
    
            // Skip to the next line so we can create the header markdown.
            chunk.startTag = chunk.endTag = "";
            chunk.skipLines(1, 1);
    
            // We make a level 2 header if there is no current header.
            // If there is a header level, we substract one from the header level.
            // If it's already a level 1 header, it's removed.
            var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
    
            if (headerLevelToCreate > 0) {
    
                // The button only creates level 1 and 2 underline headers.
                // Why not have it iterate over hash header levels?  Wouldn't that be easier and cleaner?
                var headerChar = headerLevelToCreate >= 2 ? "-" : "=";
                var len = chunk.selection.length;
                if (len > SETTINGS.lineLength) {
                    len = SETTINGS.lineLength;
                }
                chunk.endTag = "\n";
                while (len--) {
                    chunk.endTag += headerChar;
                }
            }
        };
    
        commandProto.doHorizontalRule = function (chunk, postProcessing) {
            chunk.startTag = "----------\n";
            chunk.selection = "";
            chunk.skipLines(2, 1, true);
        }
    
    
    })();
    
    define("pagedown", function(){});
    
    /*globals MD:true, Markdown */
    define('core',[
    	"underscore",
    	"crel",
    	"editor",
    	// "layout",
    	"constants",
    	"utils",
    	"storage",
    	"settings",
    	"eventMgr",
    	'pagedown'
    ], function( _, crel, editor, constants, utils, storage, settings, eventMgr) {
    
    	var core = {};
    
    	// life commonjs使用
    	MD = editor;
    
    	// Used to detect user activity
    	var isUserReal = false;
    	var userActive = false;
    	var userLastActivity = 0;
    
    	function setUserActive() {
    		isUserReal = true;
    		userActive = true;
    		var currentTime = utils.currentTime;
    		if(currentTime > userLastActivity + 1000) {
    			userLastActivity = currentTime;
    			eventMgr.onUserActive();
    		}
    	}
    
    	// Load settings in settings dialog
    	// var $themeInputElt;
    
    	// Create the PageDown editor
    	var pagedownEditor;
    	var fileDesc;
    	core.initEditorFirst = function() {
    		// Create the converter and the editor
    		var converter = new Markdown.Converter();
    		var options = {
    			_DoItalicsAndBold: function(text) {
    				// Restore original markdown implementation
    				text = text.replace(/(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\1/g,
    					"<strong>$2</strong>");
    				text = text.replace(/(\*|_)(?=\S)(.+?)(?=\S)\1/g,
    					"<em>$2</em>");
    				return text;
    			}
    		};
    		converter.setOptions(options);
    		
    		pagedownEditor = new Markdown.Editor(converter, undefined, {
    			undoManager: editor.undoMgr
    			// ,
    			// helpButton: { handler: markdownHelp },
    	        // strings: "Markdown syntax"
    		});
    
    		MD.insertLink2 = pagedownEditor.insertLink;
    
    		// Custom insert link dialog
    		pagedownEditor.hooks.set("insertLinkDialog", function(callback) {
    			core.insertLinkCallback = callback;
    			utils.resetModalInputs();
    			$(".modal-insert-link").modal();
    			return true;
    		});
    		// Custom insert image dialog
    		pagedownEditor.hooks.set("insertImageDialog", function(callback) {
                // life, atom
                insertLocalImage();
                // 上传图片
                return;
    		});
    
    		eventMgr.onPagedownConfigure(pagedownEditor);
    		pagedownEditor.hooks.chain("onPreviewRefresh", eventMgr.onAsyncPreview);
    		pagedownEditor.run();
    		// editor.undoMgr.init();
    
    		// Hide default buttons
    		$(".wmd-button-row li").addClass("btn btn-success").css("left", 0).find("span").hide();
    
    		// Add customized buttons
    		var $btnGroupElt = $('.wmd-button-group1');
    		
    		$("#wmd-bold-button").append($('<i class="fa fa-bold">')).appendTo($btnGroupElt);
    		$("#wmd-italic-button").append($('<i class="fa fa-italic">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group2');
    		$("#wmd-link-button").append($('<i class="fa fa-link">')).appendTo($btnGroupElt);
    		$("#wmd-quote-button").append($('<i class="fa fa-quote-left">')).appendTo($btnGroupElt);
    		$("#wmd-code-button").append($('<i class="fa fa-code">')).appendTo($btnGroupElt);
    		$("#wmd-image-button").append($('<i class="fa fa-picture-o">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group3');
    		$("#wmd-olist-button").append($('<i class="fa fa-list-ol">')).appendTo($btnGroupElt);
    		$("#wmd-ulist-button").append($('<i class="fa fa-list-ul">')).appendTo($btnGroupElt);
    		$("#wmd-heading-button").append($('<i class="fa fa-header">')).appendTo($btnGroupElt);
    		$("#wmd-hr-button").append($('<i class="fa fa-ellipsis-h">')).appendTo($btnGroupElt);
    		$btnGroupElt = $('.wmd-button-group5');
    		$("#wmd-undo-button").append($('<i class="fa fa-undo">')).appendTo($btnGroupElt);
    		$("#wmd-redo-button").append($('<i class="fa fa-repeat">')).appendTo($btnGroupElt);
    		$("#wmd-help-button").show();
    	};
    
    	core.initEditor = function(fileDescParam) {
    		if(fileDesc !== undefined) {
    			eventMgr.onFileClosed(fileDesc);
    		}
    		fileDesc = fileDescParam;
    
    		// If the editor is already created, 返回之
    		// 再fileDEsc有什么用?
    		if(pagedownEditor !== undefined) {
    			editor.undoMgr.init();
    			return pagedownEditor.uiManager.setUndoRedoButtonStates();
    		}
    		core.initEditorFirst();
    		editor.undoMgr.init();
    	};
    
    	// Initialize multiple things and then fire eventMgr.onReady
    	// 主入口
    	core.onReady = function() {
    		// Add RTL class
    		document.body.className += ' ' + settings.editMode;
    
    		// 这里, 以后肯定都是bodyEditorHTML, 用bodyEditorHTML不是这里加载的, 直接在html写上
    		// document.body.innerHTML = bodyEditorHTML;
    
    		// Initialize utils library
    		utils.init();
    
    		// Detect user activity
    		$(document).mousemove(setUserActive).keypress(setUserActive);
    
    		// 先发送事件, 不然partialRendering有问题
    		eventMgr.onReady();
    
    		// 布局, 一些事件, 比如打开左侧menu
    		// layout.init();
    		core.initEditorFirst();
    		editor.init();
    
    		// life
    		// var fileDesc = {content: ""};
    		// eventMgr.onFileSelected(fileDesc);
    		// core.initEditor(fileDesc);
    	};
    
    	// Other initialization that are not prioritary
    	eventMgr.addListener("onReady", function() {
    
    		$(document.body).on('shown.bs.modal', '.modal', function() {
    			var $elt = $(this);
    			setTimeout(function() {
    				// When modal opens focus on the first button
    				$elt.find('.btn:first').focus();
    				// Or on the first link if any
    				$elt.find('button:first').focus();
    				// Or on the first input if any
    				$elt.find("input:enabled:visible:first").focus();
    			}, 50);
    		}).on('hidden.bs.modal', '.modal', function() {
    			// Focus on the editor when modal is gone
    			editor.focus();
    			// Revert to current theme when settings modal is closed
    			// applyTheme(window.theme);
    		}).on('keypress', '.modal', function(e) {
    			// Handle enter key in modals
    			if(e.which == 13 && !$(e.target).is("textarea")) {
    				$(this).find(".modal-footer a:last").click();
    			}
    		});
    
    		// Click events on "insert link" and "insert image" dialog buttons
    		$(".action-insert-link").click(function(e) {
    			var value = utils.getInputTextValue($("#input-insert-link"), e);
    			if(value !== undefined) {
    				core.insertLinkCallback(value);
    				core.insertLinkCallback = undefined;
    			}
    		});
    		// 插入图片
            /*
    		$(".action-insert-image").click(function() {
    			// 得到图片链接或图片
    			var value = document.mdImageManager.mdGetImgSrc();
    			// var value = utils.getInputTextValue($("#input-insert-image"), e);
    			if(value) {
    				core.insertLinkCallback(value);
    				core.insertLinkCallback = undefined;
    			}
    		});
            */
    
    		// Hide events on "insert link" and "insert image" dialogs
    		$(".modal-insert-link, .modal-insert-image").on('hidden.bs.modal', function() {
    			if(core.insertLinkCallback !== undefined) {
    				core.insertLinkCallback(null);
    				core.insertLinkCallback = undefined;
    			}
    		});
    
    		// Avoid dropdown panels to close on click
    		$("div.dropdown-menu").click(function(e) {
    			e.stopPropagation();
    		});
    
    		// 弹框显示markdown语法
    		$('#wmd-help-button').click(function() {
                // life
                var url = 'http://leanote.com/blog/post/531b263bdfeb2c0ea9000002';
                openExternal(url);
    	        // window.open("http://leanote.com/blog/view/531b263bdfeb2c0ea9000002");
    		});
    
    		// Load images
    		_.each(document.querySelectorAll('img'), function(imgElt) {
    			var $imgElt = $(imgElt);
    			var src = $imgElt.data('stackeditSrc');
    			if(src) {
    				$imgElt.attr('src', window.baseDir + '/img/' + src);
    			}
    		});
    
    	// 	if(window.viewerMode === false) {
    	// 		// Load theme list
    	// 		var themeOptions = _.reduce(constants.THEME_LIST, function(themeOptions, name, value) {
    	// 			return themeOptions + '<option value="' + value + '">' + name + '</option>';
    	// 		}, '');
    	// 		document.getElementById('input-settings-theme').innerHTML = themeOptions;
    	// 	}
    	});
    
    	return core;
    });
    
    /*
     * Require-CSS RequireJS css! loader plugin
     * 0.1.2
     * Guy Bedford 2013
     * MIT
     */
    
    /*
     *
     * Usage:
     *  require(['css!./mycssFile']);
     *
     * Tested and working in (up to latest versions as of March 2013):
     * Android
     * iOS 6
     * IE 6 - 10
     * Chome 3 - 26
     * Firefox 3.5 - 19
     * Opera 10 - 12
     * 
     * browserling.com used for virtual testing environment
     *
     * Credit to B Cavalier & J Hann for the IE 6 - 9 method,
     * refined with help from Martin Cermak
     * 
     * Sources that helped along the way:
     * - https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
     * - http://www.phpied.com/when-is-a-stylesheet-really-loaded/
     * - https://github.com/cujojs/curl/blob/master/src/curl/plugin/css.js
     *
     */
    
    define('css/css',[],function() {
      if (typeof window == 'undefined')
        return { load: function(n, r, load){ load() } };
    
      var head = document.getElementsByTagName('head')[0];
    
      var engine = window.navigator.userAgent.match(/Trident\/([^ ;]*)|AppleWebKit\/([^ ;]*)|Opera\/([^ ;]*)|rv\:([^ ;]*)(.*?)Gecko\/([^ ;]*)|MSIE\s([^ ;]*)/) || 0;
    
      // use <style> @import load method (IE < 9, Firefox < 18)
      var useImportLoad = false;
      
      // set to false for explicit <link> load checking when onload doesn't work perfectly (webkit)
      var useOnload = true;
    
      // trident / msie
      if (engine[1] || engine[7])
        useImportLoad = parseInt(engine[1]) < 6 || parseInt(engine[7]) <= 9;
      // webkit
      else if (engine[2])
        useOnload = false;
      // gecko
      else if (engine[4])
        useImportLoad = parseInt(engine[4]) < 18;
      
      //main api object
      var cssAPI = {};
    
      cssAPI.pluginBuilder = './css-builder';
    
      // <style> @import load method
      var curStyle, curSheet;
      var createStyle = function () {
        curStyle = document.createElement('style');
        head.appendChild(curStyle);
        curSheet = curStyle.styleSheet || curStyle.sheet;
      }
      var ieCnt = 0;
      var ieLoads = [];
      var ieCurCallback;
      
      var createIeLoad = function(url) {
        ieCnt++;
        if (ieCnt == 32) {
          createStyle();
          ieCnt = 0;
        }
        curSheet.addImport(url);
        curStyle.onload = function(){ processIeLoad() };
      }
      var processIeLoad = function() {
        ieCurCallback();
     
        var nextLoad = ieLoads.shift();
     
        if (!nextLoad) {
          ieCurCallback = null;
          return;
        }
     
        ieCurCallback = nextLoad[1];
        createIeLoad(nextLoad[0]);
      }
      var importLoad = function(url, callback) {
        if (!curSheet || !curSheet.addImport)
          createStyle();
    
        if (curSheet && curSheet.addImport) {
          // old IE
          if (ieCurCallback) {
            ieLoads.push([url, callback]);
          }
          else {
            createIeLoad(url);
            ieCurCallback = callback;
          }
        }
        else {
          // old Firefox
          curStyle.textContent = '@import "' + url + '";';
    
          var loadInterval = setInterval(function() {
            try {
              curStyle.sheet.cssRules;
              clearInterval(loadInterval);
              callback();
            } catch(e) {}
          }, 10);
        }
      }
    
      // <link> load method
      var linkLoad = function(url, callback) {
        var link = document.createElement('link');
        link.type = 'text/css';
        link.rel = 'stylesheet';
        if (useOnload)
          link.onload = function() {
            link.onload = function() {};
            // for style dimensions queries, a short delay can still be necessary
            setTimeout(callback, 7);
          }
        else
          var loadInterval = setInterval(function() {
            for (var i = 0; i < document.styleSheets.length; i++) {
              var sheet = document.styleSheets[i];
              if (sheet.href == link.href) {
                clearInterval(loadInterval);
                return callback();
              }
            }
          }, 10);
        link.href = url;
        head.appendChild(link);
      }
    
      cssAPI.normalize = function(name, normalize) {
        if (name.substr(name.length - 4, 4) == '.css')
          name = name.substr(0, name.length - 4);
    
        return normalize(name);
      }
    
      cssAPI.load = function(cssId, req, load, config) {
    
        (useImportLoad ? importLoad : linkLoad)(req.toUrl(cssId + '.css'), load);
    
      }
    
      return cssAPI;
    });
    
    define('css', ['css/css'], function (main) { return main; });
    
    /**
     * @license CSS Class Applier module for Rangy.
     * Adds, removes and toggles CSS classes on Ranges and Selections
     *
     * Part of Rangy, a cross-browser JavaScript range and selection library
     * http://code.google.com/p/rangy/
     *
     * Depends on Rangy core.
     *
     * Copyright 2012, Tim Down
     * Licensed under the MIT license.
     * Version: 1.2.3
     * Build date: 26 February 2012
     */
    rangy.createModule("CssClassApplier", function(api, module) {
        api.requireModules( ["WrappedSelection", "WrappedRange"] );
    
        var dom = api.dom;
    
    
    
        var defaultTagName = "span";
    
        function trim(str) {
            return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
        }
    
        function hasClass(el, cssClass) {
            return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
        }
    
        function addClass(el, cssClass) {
            if (el.className) {
                if (!hasClass(el, cssClass)) {
                    el.className += " " + cssClass;
                }
            } else {
                el.className = cssClass;
            }
        }
    
        var removeClass = (function() {
            function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
                return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
            }
    
            return function(el, cssClass) {
                if (el.className) {
                    el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer);
                }
            };
        })();
    
        function sortClassName(className) {
            return className.split(/\s+/).sort().join(" ");
        }
    
        function getSortedClassName(el) {
            return sortClassName(el.className);
        }
    
        function haveSameClasses(el1, el2) {
            return getSortedClassName(el1) == getSortedClassName(el2);
        }
    
        function replaceWithOwnChildren(el) {
    
            var parent = el.parentNode;
            while (el.hasChildNodes()) {
                parent.insertBefore(el.firstChild, el);
            }
            parent.removeChild(el);
        }
    
        function rangeSelectsAnyText(range, textNode) {
            var textRange = range.cloneRange();
            textRange.selectNodeContents(textNode);
    
            var intersectionRange = textRange.intersection(range);
            var text = intersectionRange ? intersectionRange.toString() : "";
            textRange.detach();
    
            return text != "";
        }
    
        function getEffectiveTextNodes(range) {
            return range.getNodes([3], function(textNode) {
                return rangeSelectsAnyText(range, textNode);
            });
        }
    
        function elementsHaveSameNonClassAttributes(el1, el2) {
            if (el1.attributes.length != el2.attributes.length) return false;
            for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
                attr1 = el1.attributes[i];
                name = attr1.name;
                if (name != "class") {
                    attr2 = el2.attributes.getNamedItem(name);
                    if (attr1.specified != attr2.specified) return false;
                    if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
                }
            }
            return true;
        }
    
        function elementHasNonClassAttributes(el, exceptions) {
            for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
                attrName = el.attributes[i].name;
                if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
                    return true;
                }
            }
            return false;
        }
    
        function elementHasProps(el, props) {
            for (var p in props) {
                if (props.hasOwnProperty(p) && el[p] !== props[p]) {
                    return false;
                }
            }
            return true;
        }
    
        var getComputedStyleProperty;
    
        if (typeof window.getComputedStyle != "undefined") {
            getComputedStyleProperty = function(el, propName) {
                return dom.getWindow(el).getComputedStyle(el, null)[propName];
            };
        } else if (typeof document.documentElement.currentStyle != "undefined") {
            getComputedStyleProperty = function(el, propName) {
                return el.currentStyle[propName];
            };
        } else {
            module.fail("No means of obtaining computed style properties found");
        }
    
        var isEditableElement;
    
        (function() {
            var testEl = document.createElement("div");
            if (typeof testEl.isContentEditable == "boolean") {
                isEditableElement = function(node) {
                    return node && node.nodeType == 1 && node.isContentEditable;
                };
            } else {
                isEditableElement = function(node) {
                    if (!node || node.nodeType != 1 || node.contentEditable == "false") {
                        return false;
                    }
                    return node.contentEditable == "true" || isEditableElement(node.parentNode);
                };
            }
        })();
    
        function isEditingHost(node) {
            var parent;
            return node && node.nodeType == 1
                && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
                || (isEditableElement(node) && !isEditableElement(node.parentNode)));
        }
    
        function isEditable(node) {
            return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
        }
    
        var inlineDisplayRegex = /^inline(-block|-table)?$/i;
    
        function isNonInlineElement(node) {
            return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
        }
    
        // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
        var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
    
        function isUnrenderedWhiteSpaceNode(node) {
            if (node.data.length == 0) {
                return true;
            }
            if (htmlNonWhiteSpaceRegex.test(node.data)) {
                return false;
            }
            var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
            switch (cssWhiteSpace) {
                case "pre":
                case "pre-wrap":
                case "-moz-pre-wrap":
                    return false;
                case "pre-line":
                    if (/[\r\n]/.test(node.data)) {
                        return false;
                    }
            }
    
            // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
            // non-inline element, it will not be rendered. This seems to be a good enough definition.
            return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
        }
    
        function isSplitPoint(node, offset) {
            if (dom.isCharacterDataNode(node)) {
                if (offset == 0) {
                    return !!node.previousSibling;
                } else if (offset == node.length) {
                    return !!node.nextSibling;
                } else {
                    return true;
                }
            }
    
            return offset > 0 && offset < node.childNodes.length;
        }
    
        function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) {
            var newNode;
            var splitAtStart = (descendantOffset == 0);
    
            if (dom.isAncestorOf(descendantNode, node)) {
    
                return node;
            }
    
            if (dom.isCharacterDataNode(descendantNode)) {
                if (descendantOffset == 0) {
                    descendantOffset = dom.getNodeIndex(descendantNode);
                    descendantNode = descendantNode.parentNode;
                } else if (descendantOffset == descendantNode.length) {
                    descendantOffset = dom.getNodeIndex(descendantNode) + 1;
                    descendantNode = descendantNode.parentNode;
                } else {
                    throw module.createError("splitNodeAt should not be called with offset in the middle of a data node ("
                        + descendantOffset + " in " + descendantNode.data);
                }
            }
    
            if (isSplitPoint(descendantNode, descendantOffset)) {
                if (!newNode) {
                    newNode = descendantNode.cloneNode(false);
                    if (newNode.id) {
                        newNode.removeAttribute("id");
                    }
                    var child;
                    while ((child = descendantNode.childNodes[descendantOffset])) {
                        newNode.appendChild(child);
                    }
                    dom.insertAfter(newNode, descendantNode);
                }
                return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve);
            } else if (node != descendantNode) {
                newNode = descendantNode.parentNode;
    
                // Work out a new split point in the parent node
                var newNodeIndex = dom.getNodeIndex(descendantNode);
    
                if (!splitAtStart) {
                    newNodeIndex++;
                }
                return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve);
            }
            return node;
        }
    
        function areElementsMergeable(el1, el2) {
            return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2);
        }
    
        function createAdjacentMergeableTextNodeGetter(forward) {
            var propName = forward ? "nextSibling" : "previousSibling";
    
            return function(textNode, checkParentElement) {
                var el = textNode.parentNode;
                var adjacentNode = textNode[propName];
                if (adjacentNode) {
                    // Can merge if the node's previous/next sibling is a text node
                    if (adjacentNode && adjacentNode.nodeType == 3) {
                        return adjacentNode;
                    }
                } else if (checkParentElement) {
                    // Compare text node parent element with its sibling
                    adjacentNode = el[propName];
    
                    if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
                        return adjacentNode[forward ? "firstChild" : "lastChild"];
                    }
                }
                return null;
            }
        }
    
        var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
            getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
    
    
        function Merge(firstNode) {
            this.isElementMerge = (firstNode.nodeType == 1);
            this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
            this.textNodes = [this.firstTextNode];
        }
    
        Merge.prototype = {
            doMerge: function() {
                var textBits = [], textNode, parent, text;
                for (var i = 0, len = this.textNodes.length; i < len; ++i) {
                    textNode = this.textNodes[i];
                    parent = textNode.parentNode;
                    textBits[i] = textNode.data;
                    if (i) {
                        parent.removeChild(textNode);
                        if (!parent.hasChildNodes()) {
                            parent.parentNode.removeChild(parent);
                        }
                    }
                }
                this.firstTextNode.data = text = textBits.join("");
                return text;
            },
    
            getLength: function() {
                var i = this.textNodes.length, len = 0;
                while (i--) {
                    len += this.textNodes[i].length;
                }
                return len;
            },
    
            toString: function() {
                var textBits = [];
                for (var i = 0, len = this.textNodes.length; i < len; ++i) {
                    textBits[i] = "'" + this.textNodes[i].data + "'";
                }
                return "[Merge(" + textBits.join(",") + ")]";
            }
        };
    
        var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"];
    
        // Allow "class" as a property name in object properties
        var mappedPropertyNames = {"class" : "className"};
    
        function CssClassApplier(cssClass, options, tagNames) {
            this.cssClass = cssClass;
            var normalize, i, len, propName;
    
            var elementPropertiesFromOptions = null;
    
            // Initialize from options object
            if (typeof options == "object" && options !== null) {
                tagNames = options.tagNames;
                elementPropertiesFromOptions = options.elementProperties;
    
                for (i = 0; propName = optionProperties[i++]; ) {
                    if (options.hasOwnProperty(propName)) {
                        this[propName] = options[propName];
                    }
                }
                normalize = options.normalize;
            } else {
                normalize = options;
            }
    
            // Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization
            this.normalize = (typeof normalize == "undefined") ? true : normalize;
    
            // Initialize element properties and attribute exceptions
            this.attrExceptions = [];
            var el = document.createElement(this.elementTagName);
            this.elementProperties = {};
            for (var p in elementPropertiesFromOptions) {
                if (elementPropertiesFromOptions.hasOwnProperty(p)) {
                    // Map "class" to "className"
                    if (mappedPropertyNames.hasOwnProperty(p)) {
                        p = mappedPropertyNames[p];
                    }
                    el[p] = elementPropertiesFromOptions[p];
    
                    // Copy the property back from the dummy element so that later comparisons to check whether elements
                    // may be removed are checking against the right value. For example, the href property of an element
                    // returns a fully qualified URL even if it was previously assigned a relative URL.
                    this.elementProperties[p] = el[p];
                    this.attrExceptions.push(p);
                }
            }
    
            this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
                sortClassName(this.elementProperties.className + " " + cssClass) : cssClass;
    
            // Initialize tag names
            this.applyToAnyTagName = false;
            var type = typeof tagNames;
            if (type == "string") {
                if (tagNames == "*") {
                    this.applyToAnyTagName = true;
                } else {
                    this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
                }
            } else if (type == "object" && typeof tagNames.length == "number") {
                this.tagNames = [];
                for (i = 0, len = tagNames.length; i < len; ++i) {
                    if (tagNames[i] == "*") {
                        this.applyToAnyTagName = true;
                    } else {
                        this.tagNames.push(tagNames[i].toLowerCase());
                    }
                }
            } else {
                this.tagNames = [this.elementTagName];
            }
        }
    
        CssClassApplier.prototype = {
            elementTagName: defaultTagName,
            elementProperties: {},
            ignoreWhiteSpace: true,
            applyToEditableOnly: false,
    
            hasClass: function(node) {
                return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass);
            },
    
            getSelfOrAncestorWithClass: function(node) {
                while (node) {
                    if (this.hasClass(node, this.cssClass)) {
                        return node;
                    }
                    node = node.parentNode;
                }
                return null;
            },
    
            isModifiable: function(node) {
                return !this.applyToEditableOnly || isEditable(node);
            },
    
            // White space adjacent to an unwrappable node can be ignored for wrapping
            isIgnorableWhiteSpaceNode: function(node) {
                return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
            },
    
            // Normalizes nodes after applying a CSS class to a Range.
            postApply: function(textNodes, range, isUndo) {
    
                var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
    
                var merges = [], currentMerge;
    
                var rangeStartNode = firstNode, rangeEndNode = lastNode;
                var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
    
                var textNode, precedingTextNode;
    
                for (var i = 0, len = textNodes.length; i < len; ++i) {
                    textNode = textNodes[i];
                    precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
    
                    if (precedingTextNode) {
                        if (!currentMerge) {
                            currentMerge = new Merge(precedingTextNode);
                            merges.push(currentMerge);
                        }
                        currentMerge.textNodes.push(textNode);
                        if (textNode === firstNode) {
                            rangeStartNode = currentMerge.firstTextNode;
                            rangeStartOffset = rangeStartNode.length;
                        }
                        if (textNode === lastNode) {
                            rangeEndNode = currentMerge.firstTextNode;
                            rangeEndOffset = currentMerge.getLength();
                        }
                    } else {
                        currentMerge = null;
                    }
                }
    
                // Test whether the first node after the range needs merging
                var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
    
                if (nextTextNode) {
                    if (!currentMerge) {
                        currentMerge = new Merge(lastNode);
                        merges.push(currentMerge);
                    }
                    currentMerge.textNodes.push(nextTextNode);
                }
    
                // Do the merges
                if (merges.length) {
    
                    for (i = 0, len = merges.length; i < len; ++i) {
                        merges[i].doMerge();
                    }
    
    
                    // Set the range boundaries
                    range.setStart(rangeStartNode, rangeStartOffset);
                    range.setEnd(rangeEndNode, rangeEndOffset);
                }
    
            },
    
            createContainer: function(doc) {
                var el = doc.createElement(this.elementTagName);
                api.util.extend(el, this.elementProperties);
                addClass(el, this.cssClass);
                return el;
            },
    
            applyToTextNode: function(textNode) {
    
    
                var parent = textNode.parentNode;
                if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
                    addClass(parent, this.cssClass);
                } else {
                    var el = this.createContainer(dom.getDocument(textNode));
                    textNode.parentNode.insertBefore(el, textNode);
                    el.appendChild(textNode);
                }
    
            },
    
            isRemovable: function(el) {
                return el.tagName.toLowerCase() == this.elementTagName
                        && getSortedClassName(el) == this.elementSortedClassName
                        && elementHasProps(el, this.elementProperties)
                        && !elementHasNonClassAttributes(el, this.attrExceptions)
                        && this.isModifiable(el);
            },
    
            undoToTextNode: function(textNode, range, ancestorWithClass) {
    
                if (!range.containsNode(ancestorWithClass)) {
                    // Split out the portion of the ancestor from which we can remove the CSS class
                    //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
                    var ancestorRange = range.cloneRange();
                    ancestorRange.selectNode(ancestorWithClass);
    
                    if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) {
                        splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]);
                        range.setEndAfter(ancestorWithClass);
                    }
                    if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) {
                        ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]);
                    }
                }
    
                if (this.isRemovable(ancestorWithClass)) {
                    replaceWithOwnChildren(ancestorWithClass);
                } else {
                    removeClass(ancestorWithClass, this.cssClass);
                }
            },
    
            applyToRange: function(range) {
                range.splitBoundaries();
                var textNodes = getEffectiveTextNodes(range);
    
                if (textNodes.length) {
                    var textNode;
    
                    for (var i = 0, len = textNodes.length; i < len; ++i) {
                        textNode = textNodes[i];
    
                        if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
                                && this.isModifiable(textNode)) {
                            this.applyToTextNode(textNode);
                        }
                    }
                    range.setStart(textNodes[0], 0);
                    textNode = textNodes[textNodes.length - 1];
                    range.setEnd(textNode, textNode.length);
                    if (this.normalize) {
                        this.postApply(textNodes, range, false);
                    }
                }
            },
    
            applyToSelection: function(win) {
    
                win = win || window;
                var sel = api.getSelection(win);
    
                var range, ranges = sel.getAllRanges();
                sel.removeAllRanges();
                var i = ranges.length;
                while (i--) {
                    range = ranges[i];
                    this.applyToRange(range);
                    sel.addRange(range);
                }
    
            },
    
            undoToRange: function(range) {
    
                range.splitBoundaries();
                var textNodes = getEffectiveTextNodes(range);
                var textNode, ancestorWithClass;
                var lastTextNode = textNodes[textNodes.length - 1];
    
                if (textNodes.length) {
                    for (var i = 0, len = textNodes.length; i < len; ++i) {
                        textNode = textNodes[i];
                        ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
                        if (ancestorWithClass && this.isModifiable(textNode)) {
                            this.undoToTextNode(textNode, range, ancestorWithClass);
                        }
    
                        // Ensure the range is still valid
                        range.setStart(textNodes[0], 0);
                        range.setEnd(lastTextNode, lastTextNode.length);
                    }
    
    
    
                    if (this.normalize) {
                        this.postApply(textNodes, range, true);
                    }
                }
            },
    
            undoToSelection: function(win) {
                win = win || window;
                var sel = api.getSelection(win);
                var ranges = sel.getAllRanges(), range;
                sel.removeAllRanges();
                for (var i = 0, len = ranges.length; i < len; ++i) {
                    range = ranges[i];
                    this.undoToRange(range);
                    sel.addRange(range);
                }
            },
    
            getTextSelectedByRange: function(textNode, range) {
                var textRange = range.cloneRange();
                textRange.selectNodeContents(textNode);
    
                var intersectionRange = textRange.intersection(range);
                var text = intersectionRange ? intersectionRange.toString() : "";
                textRange.detach();
    
                return text;
            },
    
            isAppliedToRange: function(range) {
                if (range.collapsed) {
                    return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
                } else {
                    var textNodes = range.getNodes( [3] );
                    for (var i = 0, textNode; textNode = textNodes[i++]; ) {
                        if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
                                && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
                            return false;
                        }
                    }
                    return true;
                }
            },
    
            isAppliedToSelection: function(win) {
                win = win || window;
                var sel = api.getSelection(win);
                var ranges = sel.getAllRanges();
                var i = ranges.length;
                while (i--) {
                    if (!this.isAppliedToRange(ranges[i])) {
                        return false;
                    }
                }
    
                return true;
            },
    
            toggleRange: function(range) {
                if (this.isAppliedToRange(range)) {
                    this.undoToRange(range);
                } else {
                    this.applyToRange(range);
                }
            },
    
            toggleSelection: function(win) {
                if (this.isAppliedToSelection(win)) {
                    this.undoToSelection(win);
                } else {
                    this.applyToSelection(win);
                }
            },
    
            detach: function() {}
        };
    
        function createCssClassApplier(cssClass, options, tagNames) {
            return new CssClassApplier(cssClass, options, tagNames);
        }
    
        CssClassApplier.util = {
            hasClass: hasClass,
            addClass: addClass,
            removeClass: removeClass,
            hasSameClasses: haveSameClasses,
            replaceWithOwnChildren: replaceWithOwnChildren,
            elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
            elementHasNonClassAttributes: elementHasNonClassAttributes,
            splitNodeAt: splitNodeAt,
            isEditableElement: isEditableElement,
            isEditingHost: isEditingHost,
            isEditable: isEditable
        };
    
        api.CssClassApplier = CssClassApplier;
        api.createCssClassApplier = createCssClassApplier;
    });
    
    define("rangy-cssclassapplier", function(){});
    
    /*
     * jQuery UI Widget 1.10.1+amd
     * https://github.com/blueimp/jQuery-File-Upload
     *
     * Copyright 2013 jQuery Foundation and other contributors
     * Released under the MIT license.
     * http://jquery.org/license
     *
     * http://api.jqueryui.com/jQuery.widget/
     */
    
    (function (factory) {
    	/* 需要jquery, 会重新加载jquery, slimScroll会覆盖
        if (typeof define === "function" && define.amd) {
            // Register as an anonymous AMD module:
            define(["jquery"], factory);
        } else {
            // Browser globals:
            factory(jQuery);
        }
        */
        
        factory(jQuery);
    }(function( $, undefined ) {
    
    var uuid = 0,
    	slice = Array.prototype.slice,
    	_cleanData = $.cleanData;
    $.cleanData = function( elems ) {
    	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
    		try {
    			$( elem ).triggerHandler( "remove" );
    		// http://bugs.jquery.com/ticket/8235
    		} catch( e ) {}
    	}
    	_cleanData( elems );
    };
    
    $.widget = function( name, base, prototype ) {
    	var fullName, existingConstructor, constructor, basePrototype,
    		// proxiedPrototype allows the provided prototype to remain unmodified
    		// so that it can be used as a mixin for multiple widgets (#8876)
    		proxiedPrototype = {},
    		namespace = name.split( "." )[ 0 ];
    
    	name = name.split( "." )[ 1 ];
    	fullName = namespace + "-" + name;
    
    	if ( !prototype ) {
    		prototype = base;
    		base = $.Widget;
    	}
    
    	// create selector for plugin
    	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
    		return !!$.data( elem, fullName );
    	};
    
    	$[ namespace ] = $[ namespace ] || {};
    	existingConstructor = $[ namespace ][ name ];
    	constructor = $[ namespace ][ name ] = function( options, element ) {
    		// allow instantiation without "new" keyword
    		if ( !this._createWidget ) {
    			return new constructor( options, element );
    		}
    
    		// allow instantiation without initializing for simple inheritance
    		// must use "new" keyword (the code above always passes args)
    		if ( arguments.length ) {
    			this._createWidget( options, element );
    		}
    	};
    	// extend with the existing constructor to carry over any static properties
    	$.extend( constructor, existingConstructor, {
    		version: prototype.version,
    		// copy the object used to create the prototype in case we need to
    		// redefine the widget later
    		_proto: $.extend( {}, prototype ),
    		// track widgets that inherit from this widget in case this widget is
    		// redefined after a widget inherits from it
    		_childConstructors: []
    	});
    
    	basePrototype = new base();
    	// we need to make the options hash a property directly on the new instance
    	// otherwise we'll modify the options hash on the prototype that we're
    	// inheriting from
    	basePrototype.options = $.widget.extend( {}, basePrototype.options );
    	$.each( prototype, function( prop, value ) {
    		if ( !$.isFunction( value ) ) {
    			proxiedPrototype[ prop ] = value;
    			return;
    		}
    		proxiedPrototype[ prop ] = (function() {
    			var _super = function() {
    					return base.prototype[ prop ].apply( this, arguments );
    				},
    				_superApply = function( args ) {
    					return base.prototype[ prop ].apply( this, args );
    				};
    			return function() {
    				var __super = this._super,
    					__superApply = this._superApply,
    					returnValue;
    
    				this._super = _super;
    				this._superApply = _superApply;
    
    				returnValue = value.apply( this, arguments );
    
    				this._super = __super;
    				this._superApply = __superApply;
    
    				return returnValue;
    			};
    		})();
    	});
    	constructor.prototype = $.widget.extend( basePrototype, {
    		// TODO: remove support for widgetEventPrefix
    		// always use the name + a colon as the prefix, e.g., draggable:start
    		// don't prefix for widgets that aren't DOM-based
    		widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
    	}, proxiedPrototype, {
    		constructor: constructor,
    		namespace: namespace,
    		widgetName: name,
    		widgetFullName: fullName
    	});
    
    	// If this widget is being redefined then we need to find all widgets that
    	// are inheriting from it and redefine all of them so that they inherit from
    	// the new version of this widget. We're essentially trying to replace one
    	// level in the prototype chain.
    	if ( existingConstructor ) {
    		$.each( existingConstructor._childConstructors, function( i, child ) {
    			var childPrototype = child.prototype;
    
    			// redefine the child widget using the same prototype that was
    			// originally used, but inherit from the new version of the base
    			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
    		});
    		// remove the list of existing child constructors from the old constructor
    		// so the old child constructors can be garbage collected
    		delete existingConstructor._childConstructors;
    	} else {
    		base._childConstructors.push( constructor );
    	}
    
    	$.widget.bridge( name, constructor );
    };
    
    $.widget.extend = function( target ) {
    	var input = slice.call( arguments, 1 ),
    		inputIndex = 0,
    		inputLength = input.length,
    		key,
    		value;
    	for ( ; inputIndex < inputLength; inputIndex++ ) {
    		for ( key in input[ inputIndex ] ) {
    			value = input[ inputIndex ][ key ];
    			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
    				// Clone objects
    				if ( $.isPlainObject( value ) ) {
    					target[ key ] = $.isPlainObject( target[ key ] ) ?
    						$.widget.extend( {}, target[ key ], value ) :
    						// Don't extend strings, arrays, etc. with objects
    						$.widget.extend( {}, value );
    				// Copy everything else by reference
    				} else {
    					target[ key ] = value;
    				}
    			}
    		}
    	}
    	return target;
    };
    
    $.widget.bridge = function( name, object ) {
    	var fullName = object.prototype.widgetFullName || name;
    	$.fn[ name ] = function( options ) {
    		var isMethodCall = typeof options === "string",
    			args = slice.call( arguments, 1 ),
    			returnValue = this;
    
    		// allow multiple hashes to be passed on init
    		options = !isMethodCall && args.length ?
    			$.widget.extend.apply( null, [ options ].concat(args) ) :
    			options;
    
    		if ( isMethodCall ) {
    			this.each(function() {
    				var methodValue,
    					instance = $.data( this, fullName );
    				if ( !instance ) {
    					return $.error( "cannot call methods on " + name + " prior to initialization; " +
    						"attempted to call method '" + options + "'" );
    				}
    				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
    					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
    				}
    				methodValue = instance[ options ].apply( instance, args );
    				if ( methodValue !== instance && methodValue !== undefined ) {
    					returnValue = methodValue && methodValue.jquery ?
    						returnValue.pushStack( methodValue.get() ) :
    						methodValue;
    					return false;
    				}
    			});
    		} else {
    			this.each(function() {
    				var instance = $.data( this, fullName );
    				if ( instance ) {
    					instance.option( options || {} )._init();
    				} else {
    					$.data( this, fullName, new object( options, this ) );
    				}
    			});
    		}
    
    		return returnValue;
    	};
    };
    
    $.Widget = function( /* options, element */ ) {};
    $.Widget._childConstructors = [];
    
    $.Widget.prototype = {
    	widgetName: "widget",
    	widgetEventPrefix: "",
    	defaultElement: "<div>",
    	options: {
    		disabled: false,
    
    		// callbacks
    		create: null
    	},
    	_createWidget: function( options, element ) {
    		element = $( element || this.defaultElement || this )[ 0 ];
    		this.element = $( element );
    		this.uuid = uuid++;
    		this.eventNamespace = "." + this.widgetName + this.uuid;
    		this.options = $.widget.extend( {},
    			this.options,
    			this._getCreateOptions(),
    			options );
    
    		this.bindings = $();
    		this.hoverable = $();
    		this.focusable = $();
    
    		if ( element !== this ) {
    			$.data( element, this.widgetFullName, this );
    			this._on( true, this.element, {
    				remove: function( event ) {
    					if ( event.target === element ) {
    						this.destroy();
    					}
    				}
    			});
    			this.document = $( element.style ?
    				// element within the document
    				element.ownerDocument :
    				// element is window or document
    				element.document || element );
    			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
    		}
    
    		this._create();
    		this._trigger( "create", null, this._getCreateEventData() );
    		this._init();
    	},
    	_getCreateOptions: $.noop,
    	_getCreateEventData: $.noop,
    	_create: $.noop,
    	_init: $.noop,
    
    	destroy: function() {
    		this._destroy();
    		// we can probably remove the unbind calls in 2.0
    		// all event bindings should go through this._on()
    		this.element
    			.unbind( this.eventNamespace )
    			// 1.9 BC for #7810
    			// TODO remove dual storage
    			.removeData( this.widgetName )
    			.removeData( this.widgetFullName )
    			// support: jquery <1.6.3
    			// http://bugs.jquery.com/ticket/9413
    			.removeData( $.camelCase( this.widgetFullName ) );
    		this.widget()
    			.unbind( this.eventNamespace )
    			.removeAttr( "aria-disabled" )
    			.removeClass(
    				this.widgetFullName + "-disabled " +
    				"ui-state-disabled" );
    
    		// clean up events and states
    		this.bindings.unbind( this.eventNamespace );
    		this.hoverable.removeClass( "ui-state-hover" );
    		this.focusable.removeClass( "ui-state-focus" );
    	},
    	_destroy: $.noop,
    
    	widget: function() {
    		return this.element;
    	},
    
    	option: function( key, value ) {
    		var options = key,
    			parts,
    			curOption,
    			i;
    
    		if ( arguments.length === 0 ) {
    			// don't return a reference to the internal hash
    			return $.widget.extend( {}, this.options );
    		}
    
    		if ( typeof key === "string" ) {
    			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
    			options = {};
    			parts = key.split( "." );
    			key = parts.shift();
    			if ( parts.length ) {
    				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
    				for ( i = 0; i < parts.length - 1; i++ ) {
    					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
    					curOption = curOption[ parts[ i ] ];
    				}
    				key = parts.pop();
    				if ( value === undefined ) {
    					return curOption[ key ] === undefined ? null : curOption[ key ];
    				}
    				curOption[ key ] = value;
    			} else {
    				if ( value === undefined ) {
    					return this.options[ key ] === undefined ? null : this.options[ key ];
    				}
    				options[ key ] = value;
    			}
    		}
    
    		this._setOptions( options );
    
    		return this;
    	},
    	_setOptions: function( options ) {
    		var key;
    
    		for ( key in options ) {
    			this._setOption( key, options[ key ] );
    		}
    
    		return this;
    	},
    	_setOption: function( key, value ) {
    		this.options[ key ] = value;
    
    		if ( key === "disabled" ) {
    			this.widget()
    				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
    				.attr( "aria-disabled", value );
    			this.hoverable.removeClass( "ui-state-hover" );
    			this.focusable.removeClass( "ui-state-focus" );
    		}
    
    		return this;
    	},
    
    	enable: function() {
    		return this._setOption( "disabled", false );
    	},
    	disable: function() {
    		return this._setOption( "disabled", true );
    	},
    
    	_on: function( suppressDisabledCheck, element, handlers ) {
    		var delegateElement,
    			instance = this;
    
    		// no suppressDisabledCheck flag, shuffle arguments
    		if ( typeof suppressDisabledCheck !== "boolean" ) {
    			handlers = element;
    			element = suppressDisabledCheck;
    			suppressDisabledCheck = false;
    		}
    
    		// no element argument, shuffle and use this.element
    		if ( !handlers ) {
    			handlers = element;
    			element = this.element;
    			delegateElement = this.widget();
    		} else {
    			// accept selectors, DOM elements
    			element = delegateElement = $( element );
    			this.bindings = this.bindings.add( element );
    		}
    
    		$.each( handlers, function( event, handler ) {
    			function handlerProxy() {
    				// allow widgets to customize the disabled handling
    				// - disabled as an array instead of boolean
    				// - disabled class as method for disabling individual parts
    				if ( !suppressDisabledCheck &&
    						( instance.options.disabled === true ||
    							$( this ).hasClass( "ui-state-disabled" ) ) ) {
    					return;
    				}
    				return ( typeof handler === "string" ? instance[ handler ] : handler )
    					.apply( instance, arguments );
    			}
    
    			// copy the guid so direct unbinding works
    			if ( typeof handler !== "string" ) {
    				handlerProxy.guid = handler.guid =
    					handler.guid || handlerProxy.guid || $.guid++;
    			}
    
    			var match = event.match( /^(\w+)\s*(.*)$/ ),
    				eventName = match[1] + instance.eventNamespace,
    				selector = match[2];
    			if ( selector ) {
    				delegateElement.delegate( selector, eventName, handlerProxy );
    			} else {
    				element.bind( eventName, handlerProxy );
    			}
    		});
    	},
    
    	_off: function( element, eventName ) {
    		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
    		element.unbind( eventName ).undelegate( eventName );
    	},
    
    	_delay: function( handler, delay ) {
    		function handlerProxy() {
    			return ( typeof handler === "string" ? instance[ handler ] : handler )
    				.apply( instance, arguments );
    		}
    		var instance = this;
    		return setTimeout( handlerProxy, delay || 0 );
    	},
    
    	_hoverable: function( element ) {
    		this.hoverable = this.hoverable.add( element );
    		this._on( element, {
    			mouseenter: function( event ) {
    				$( event.currentTarget ).addClass( "ui-state-hover" );
    			},
    			mouseleave: function( event ) {
    				$( event.currentTarget ).removeClass( "ui-state-hover" );
    			}
    		});
    	},
    
    	_focusable: function( element ) {
    		this.focusable = this.focusable.add( element );
    		this._on( element, {
    			focusin: function( event ) {
    				$( event.currentTarget ).addClass( "ui-state-focus" );
    			},
    			focusout: function( event ) {
    				$( event.currentTarget ).removeClass( "ui-state-focus" );
    			}
    		});
    	},
    
    	_trigger: function( type, event, data ) {
    		var prop, orig,
    			callback = this.options[ type ];
    
    		data = data || {};
    		event = $.Event( event );
    		event.type = ( type === this.widgetEventPrefix ?
    			type :
    			this.widgetEventPrefix + type ).toLowerCase();
    		// the original event may come from any element
    		// so we need to reset the target on the new event
    		event.target = this.element[ 0 ];
    
    		// copy original event properties over to the new event
    		orig = event.originalEvent;
    		if ( orig ) {
    			for ( prop in orig ) {
    				if ( !( prop in event ) ) {
    					event[ prop ] = orig[ prop ];
    				}
    			}
    		}
    
    		this.element.trigger( event, data );
    		return !( $.isFunction( callback ) &&
    			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
    			event.isDefaultPrevented() );
    	}
    };
    
    $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
    	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
    		if ( typeof options === "string" ) {
    			options = { effect: options };
    		}
    		var hasOptions,
    			effectName = !options ?
    				method :
    				options === true || typeof options === "number" ?
    					defaultEffect :
    					options.effect || defaultEffect;
    		options = options || {};
    		if ( typeof options === "number" ) {
    			options = { duration: options };
    		}
    		hasOptions = !$.isEmptyObject( options );
    		options.complete = callback;
    		if ( options.delay ) {
    			element.delay( options.delay );
    		}
    		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
    			element[ method ]( options );
    		} else if ( effectName !== method && element[ effectName ] ) {
    			element[ effectName ]( options.duration, options.easing, callback );
    		} else {
    			element.queue(function( next ) {
    				$( this )[ method ]();
    				if ( callback ) {
    					callback.call( element[ 0 ] );
    				}
    				next();
    			});
    		}
    	};
    });
    
    }));
    
    define("jquery.ui.widget", function(){});
    
    /*
     * jQuery Iframe Transport Plugin 1.6.1
     * https://github.com/blueimp/jQuery-File-Upload
     *
     * Copyright 2011, Sebastian Tschan
     * https://blueimp.net
     *
     * Licensed under the MIT license:
     * http://www.opensource.org/licenses/MIT
     */
    
    /*jslint unparam: true, nomen: true */
    /*global define, window, document */
    
    (function (factory) {
        
        /*
        if (typeof define === 'function' && define.amd) {
            // Register as an anonymous AMD module:
            define(['jquery'], factory);
        } else {
            // Browser globals:
            factory(window.jQuery);
        }
        */
        factory(window.jQuery);
    }(function ($) {
        
    
        // Helper variable to create unique names for the transport iframes:
        var counter = 0;
    
        // The iframe transport accepts three additional options:
        // options.fileInput: a jQuery collection of file input fields
        // options.paramName: the parameter name for the file form data,
        //  overrides the name property of the file input field(s),
        //  can be a string or an array of strings.
        // options.formData: an array of objects with name and value properties,
        //  equivalent to the return data of .serializeArray(), e.g.:
        //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
        $.ajaxTransport('iframe', function (options) {
            if (options.async) {
                var form,
                    iframe,
                    addParamChar;
                return {
                    send: function (_, completeCallback) {
                        form = $('<form style="display:none;"></form>');
                        form.attr('accept-charset', options.formAcceptCharset);
                        addParamChar = /\?/.test(options.url) ? '&' : '?';
                        // XDomainRequest only supports GET and POST:
                        if (options.type === 'DELETE') {
                            options.url = options.url + addParamChar + '_method=DELETE';
                            options.type = 'POST';
                        } else if (options.type === 'PUT') {
                            options.url = options.url + addParamChar + '_method=PUT';
                            options.type = 'POST';
                        } else if (options.type === 'PATCH') {
                            options.url = options.url + addParamChar + '_method=PATCH';
                            options.type = 'POST';
                        }
                        // javascript:false as initial iframe src
                        // prevents warning popups on HTTPS in IE6.
                        // IE versions below IE8 cannot set the name property of
                        // elements that have already been added to the DOM,
                        // so we set the name along with the iframe HTML markup:
                        iframe = $(
                            '<iframe src="javascript:false;" name="iframe-transport-' +
                                (counter += 1) + '"></iframe>'
                        ).bind('load', function () {
                            var fileInputClones,
                                paramNames = $.isArray(options.paramName) ?
                                        options.paramName : [options.paramName];
                            iframe
                                .unbind('load')
                                .bind('load', function () {
                                    var response;
                                    // Wrap in a try/catch block to catch exceptions thrown
                                    // when trying to access cross-domain iframe contents:
                                    try {
                                        response = iframe.contents();
                                        // Google Chrome and Firefox do not throw an
                                        // exception when calling iframe.contents() on
                                        // cross-domain requests, so we unify the response:
                                        if (!response.length || !response[0].firstChild) {
                                            throw new Error();
                                        }
                                    } catch (e) {
                                        response = undefined;
                                    }
                                    // The complete callback returns the
                                    // iframe content document as response object:
                                    completeCallback(
                                        200,
                                        'success',
                                        {'iframe': response}
                                    );
                                    // Fix for IE endless progress bar activity bug
                                    // (happens on form submits to iframe targets):
                                    $('<iframe src="javascript:false;"></iframe>')
                                        .appendTo(form);
                                    form.remove();
                                });
                            form
                                .prop('target', iframe.prop('name'))
                                .prop('action', options.url)
                                .prop('method', options.type);
                            if (options.formData) {
                                $.each(options.formData, function (index, field) {
                                    $('<input type="hidden"/>')
                                        .prop('name', field.name)
                                        .val(field.value)
                                        .appendTo(form);
                                });
                            }
                            if (options.fileInput && options.fileInput.length &&
                                    options.type === 'POST') {
                                fileInputClones = options.fileInput.clone();
                                // Insert a clone for each file input field:
                                options.fileInput.after(function (index) {
                                    return fileInputClones[index];
                                });
                                if (options.paramName) {
                                    options.fileInput.each(function (index) {
                                        $(this).prop(
                                            'name',
                                            paramNames[index] || options.paramName
                                        );
                                    });
                                }
                                // Appending the file input fields to the hidden form
                                // removes them from their original location:
                                form
                                    .append(options.fileInput)
                                    .prop('enctype', 'multipart/form-data')
                                    // enctype must be set as encoding for IE:
                                    .prop('encoding', 'multipart/form-data');
                            }
                            form.submit();
                            // Insert the file input fields at their original location
                            // by replacing the clones with the originals:
                            if (fileInputClones && fileInputClones.length) {
                                options.fileInput.each(function (index, input) {
                                    var clone = $(fileInputClones[index]);
                                    $(input).prop('name', clone.prop('name'));
                                    clone.replaceWith(input);
                                });
                            }
                        });
                        form.append(iframe).appendTo(document.body);
                    },
                    abort: function () {
                        if (iframe) {
                            // javascript:false as iframe src aborts the request
                            // and prevents warning popups on HTTPS in IE6.
                            // concat is used to avoid the "Script URL" JSLint error:
                            iframe
                                .unbind('load')
                                .prop('src', 'javascript'.concat(':false;'));
                        }
                        if (form) {
                            form.remove();
                        }
                    }
                };
            }
        });
    
        // The iframe transport returns the iframe content document as response.
        // The following adds converters from iframe to text, json, html, and script:
        $.ajaxSetup({
            converters: {
                'iframe text': function (iframe) {
                    return iframe && $(iframe[0].body).text();
                },
                'iframe json': function (iframe) {
                    return iframe && $.parseJSON($(iframe[0].body).text());
                },
                'iframe html': function (iframe) {
                    return iframe && $(iframe[0].body).html();
                },
                'iframe script': function (iframe) {
                    return iframe && $.globalEval($(iframe[0].body).text());
                }
            }
        });
    
    }));
    
    define("iframe-transport", function(){});
    
    /*
     * jQuery File Upload Plugin 5.26
     * https://github.com/blueimp/jQuery-File-Upload
     *
     * Copyright 2010, Sebastian Tschan
     * https://blueimp.net
     *
     * Licensed under the MIT license:
     * http://www.opensource.org/licenses/MIT
     */
    
    /*jslint nomen: true, unparam: true, regexp: true */
    /*global define, window, document, File, Blob, FormData, location */
    
    (function (factory) {
        
        // 不要jquery, 使用全局的jquery
        /*
        if (typeof define === 'function' && define.amd) {
            // Register as an anonymous AMD module:
            define([
                'jquery',
                'jquery.ui.widget'
            ], factory);
        } else {
            // Browser globals:
        }
        */
        factory(window.jQuery);
    }(function ($) {
        
    
        // The FileReader API is not actually used, but works as feature detection,
        // as e.g. Safari supports XHR file uploads via the FormData API,
        // but not non-multipart XHR file uploads:
        $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
        $.support.xhrFormDataFileUpload = !!window.FormData;
    
        // The fileupload widget listens for change events on file input fields defined
        // via fileInput setting and paste or drop events of the given dropZone.
        // In addition to the default jQuery Widget methods, the fileupload widget
        // exposes the "add" and "send" methods, to add or directly send files using
        // the fileupload API.
        // By default, files added via file input selection, paste, drag & drop or
        // "add" method are uploaded immediately, but it is possible to override
        // the "add" callback option to queue file uploads.
        $.widget('blueimp.fileupload', {
    
            options: {
                // The drop target element(s), by the default the complete document.
                // Set to null to disable drag & drop support:
                dropZone: $(document),
                // The paste target element(s), by the default the complete document.
                // Set to null to disable paste support:
                pasteZone: $(document),
                // The file input field(s), that are listened to for change events.
                // If undefined, it is set to the file input fields inside
                // of the widget element on plugin initialization.
                // Set to null to disable the change listener.
                fileInput: undefined,
                // By default, the file input field is replaced with a clone after
                // each input field change event. This is required for iframe transport
                // queues and allows change events to be fired for the same file
                // selection, but can be disabled by setting the following option to false:
                replaceFileInput: true,
                // The parameter name for the file form data (the request argument name).
                // If undefined or empty, the name property of the file input field is
                // used, or "files[]" if the file input name property is also empty,
                // can be a string or an array of strings:
                paramName: undefined,
                // By default, each file of a selection is uploaded using an individual
                // request for XHR type uploads. Set to false to upload file
                // selections in one request each:
                singleFileUploads: true,
                // To limit the number of files uploaded with one XHR request,
                // set the following option to an integer greater than 0:
                limitMultiFileUploads: undefined,
                // Set the following option to true to issue all file upload requests
                // in a sequential order:
                sequentialUploads: false,
                // To limit the number of concurrent uploads,
                // set the following option to an integer greater than 0:
                limitConcurrentUploads: undefined,
                // Set the following option to true to force iframe transport uploads:
                forceIframeTransport: false,
                // Set the following option to the location of a redirect url on the
                // origin server, for cross-domain iframe transport uploads:
                redirect: undefined,
                // The parameter name for the redirect url, sent as part of the form
                // data and set to 'redirect' if this option is empty:
                redirectParamName: undefined,
                // Set the following option to the location of a postMessage window,
                // to enable postMessage transport uploads:
                postMessage: undefined,
                // By default, XHR file uploads are sent as multipart/form-data.
                // The iframe transport is always using multipart/form-data.
                // Set to false to enable non-multipart XHR uploads:
                multipart: true,
                // To upload large files in smaller chunks, set the following option
                // to a preferred maximum chunk size. If set to 0, null or undefined,
                // or the browser does not support the required Blob API, files will
                // be uploaded as a whole.
                maxChunkSize: undefined,
                // When a non-multipart upload or a chunked multipart upload has been
                // aborted, this option can be used to resume the upload by setting
                // it to the size of the already uploaded bytes. This option is most
                // useful when modifying the options object inside of the "add" or
                // "send" callbacks, as the options are cloned for each file upload.
                uploadedBytes: undefined,
                // By default, failed (abort or error) file uploads are removed from the
                // global progress calculation. Set the following option to false to
                // prevent recalculating the global progress data:
                recalculateProgress: true,
                // Interval in milliseconds to calculate and trigger progress events:
                progressInterval: 100,
                // Interval in milliseconds to calculate progress bitrate:
                bitrateInterval: 500,
                // By default, uploads are started automatically when adding files:
                autoUpload: true,
    
                // Additional form data to be sent along with the file uploads can be set
                // using this option, which accepts an array of objects with name and
                // value properties, a function returning such an array, a FormData
                // object (for XHR file uploads), or a simple object.
                // The form of the first fileInput is given as parameter to the function:
                formData: function (form) {
                    return form.serializeArray();
                },
    
                // The add callback is invoked as soon as files are added to the fileupload
                // widget (via file input selection, drag & drop, paste or add API call).
                // If the singleFileUploads option is enabled, this callback will be
                // called once for each file in the selection for XHR file uplaods, else
                // once for each file selection.
                // The upload starts when the submit method is invoked on the data parameter.
                // The data object contains a files property holding the added files
                // and allows to override plugin options as well as define ajax settings.
                // Listeners for this callback can also be bound the following way:
                // .bind('fileuploadadd', func);
                // data.submit() returns a Promise object and allows to attach additional
                // handlers using jQuery's Deferred callbacks:
                // data.submit().done(func).fail(func).always(func);
                add: function (e, data) {
                    if (data.autoUpload || (data.autoUpload !== false &&
                            ($(this).data('blueimp-fileupload') ||
                            $(this).data('fileupload')).options.autoUpload)) {
                        data.submit();
                    }
                },
    
                // Other callbacks:
    
                // Callback for the submit event of each file upload:
                // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
    
                // Callback for the start of each file upload request:
                // send: function (e, data) {}, // .bind('fileuploadsend', func);
    
                // Callback for successful uploads:
                // done: function (e, data) {}, // .bind('fileuploaddone', func);
    
                // Callback for failed (abort or error) uploads:
                // fail: function (e, data) {}, // .bind('fileuploadfail', func);
    
                // Callback for completed (success, abort or error) requests:
                // always: function (e, data) {}, // .bind('fileuploadalways', func);
    
                // Callback for upload progress events:
                // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
    
                // Callback for global upload progress events:
                // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
    
                // Callback for uploads start, equivalent to the global ajaxStart event:
                // start: function (e) {}, // .bind('fileuploadstart', func);
    
                // Callback for uploads stop, equivalent to the global ajaxStop event:
                // stop: function (e) {}, // .bind('fileuploadstop', func);
    
                // Callback for change events of the fileInput(s):
                // change: function (e, data) {}, // .bind('fileuploadchange', func);
    
                // Callback for paste events to the pasteZone(s):
                // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
    
                // Callback for drop events of the dropZone(s):
                // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
    
                // Callback for dragover events of the dropZone(s):
                // dragover: function (e) {}, // .bind('fileuploaddragover', func);
    
                // Callback for the start of each chunk upload request:
                // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
    
                // Callback for successful chunk uploads:
                // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
    
                // Callback for failed (abort or error) chunk uploads:
                // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
    
                // Callback for completed (success, abort or error) chunk upload requests:
                // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
    
                // The plugin options are used as settings object for the ajax calls.
                // The following are jQuery ajax settings required for the file uploads:
                processData: false,
                contentType: false,
                cache: false
            },
    
            // A list of options that require a refresh after assigning a new value:
            _refreshOptionsList: [
                'fileInput',
                'dropZone',
                'pasteZone',
                'multipart',
                'forceIframeTransport'
            ],
    
            _BitrateTimer: function () {
                this.timestamp = +(new Date());
                this.loaded = 0;
                this.bitrate = 0;
                this.getBitrate = function (now, loaded, interval) {
                    var timeDiff = now - this.timestamp;
                    if (!this.bitrate || !interval || timeDiff > interval) {
                        this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
                        this.loaded = loaded;
                        this.timestamp = now;
                    }
                    return this.bitrate;
                };
            },
    
            _isXHRUpload: function (options) {
                return !options.forceIframeTransport &&
                    ((!options.multipart && $.support.xhrFileUpload) ||
                    $.support.xhrFormDataFileUpload);
            },
    
            _getFormData: function (options) {
                var formData;
                if (typeof options.formData === 'function') {
                    return options.formData(options.form);
                }
                if ($.isArray(options.formData)) {
                    return options.formData;
                }
                if (options.formData) {
                    formData = [];
                    $.each(options.formData, function (name, value) {
                        formData.push({name: name, value: value});
                    });
                    return formData;
                }
                return [];
            },
    
            _getTotal: function (files) {
                var total = 0;
                $.each(files, function (index, file) {
                    total += file.size || 1;
                });
                return total;
            },
    
            _initProgressObject: function (obj) {
                obj._progress = {
                    loaded: 0,
                    total: 0,
                    bitrate: 0
                };
            },
    
            _onProgress: function (e, data) {
                if (e.lengthComputable) {
                    var now = +(new Date()),
                        loaded;
                    if (data._time && data.progressInterval &&
                            (now - data._time < data.progressInterval) &&
                            e.loaded !== e.total) {
                        return;
                    }
                    data._time = now;
                    loaded = Math.floor(
                        e.loaded / e.total * (data.chunkSize || data._progress.total)
                    ) + (data.uploadedBytes || 0);
                    // Add the difference from the previously loaded state
                    // to the global loaded counter:
                    this._progress.loaded += (loaded - data._progress.loaded);
                    this._progress.bitrate = this._bitrateTimer.getBitrate(
                        now,
                        this._progress.loaded,
                        data.bitrateInterval
                    );
                    data._progress.loaded = data.loaded = loaded;
                    data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
                        now,
                        loaded,
                        data.bitrateInterval
                    );
                    // Trigger a custom progress event with a total data property set
                    // to the file size(s) of the current upload and a loaded data
                    // property calculated accordingly:
                    this._trigger('progress', e, data);
                    // Trigger a global progress event for all current file uploads,
                    // including ajax calls queued for sequential file uploads:
                    this._trigger('progressall', e, this._progress);
                }
            },
    
            _initProgressListener: function (options) {
                var that = this,
                    xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
                // Accesss to the native XHR object is required to add event listeners
                // for the upload progress event:
                if (xhr.upload) {
                    $(xhr.upload).bind('progress', function (e) {
                        var oe = e.originalEvent;
                        // Make sure the progress event properties get copied over:
                        e.lengthComputable = oe.lengthComputable;
                        e.loaded = oe.loaded;
                        e.total = oe.total;
                        that._onProgress(e, options);
                    });
                    options.xhr = function () {
                        return xhr;
                    };
                }
            },
    
            _initXHRData: function (options) {
                var formData,
                    file = options.files[0],
                    // Ignore non-multipart setting if not supported:
                    multipart = options.multipart || !$.support.xhrFileUpload,
                    paramName = options.paramName[0];
                options.headers = options.headers || {};
                if (options.contentRange) {
                    options.headers['Content-Range'] = options.contentRange;
                }
                if (!multipart) {
                    options.headers['Content-Disposition'] = 'attachment; filename="' +
                        encodeURI(file.name) + '"';
                    options.contentType = file.type;
                    options.data = options.blob || file;
                } else if ($.support.xhrFormDataFileUpload) {
                    if (options.postMessage) {
                        // window.postMessage does not allow sending FormData
                        // objects, so we just add the File/Blob objects to
                        // the formData array and let the postMessage window
                        // create the FormData object out of this array:
                        formData = this._getFormData(options);
                        if (options.blob) {
                            formData.push({
                                name: paramName,
                                value: options.blob
                            });
                        } else {
                            $.each(options.files, function (index, file) {
                                formData.push({
                                    name: options.paramName[index] || paramName,
                                    value: file
                                });
                            });
                        }
                    } else {
                        if (options.formData instanceof FormData) {
                            formData = options.formData;
                        } else {
                            formData = new FormData();
                            var a = this._getFormData(options);
                            log(a);
                            $.each(this._getFormData(options), function (index, field) {
                                formData.append(field.name, field.value);
                            });
                        }
                        if (options.blob) {
                            options.headers['Content-Disposition'] = 'attachment; filename="' +
                                encodeURI(file.name) + '"';
                            formData.append(paramName, options.blob, file.name);
                        } else {
                            $.each(options.files, function (index, file) {
                                // Files are also Blob instances, but some browsers
                                // (Firefox 3.6) support the File API but not Blobs.
                                // This check allows the tests to run with
                                // dummy objects:
                                if ((window.Blob && file instanceof Blob) ||
                                        (window.File && file instanceof File)) {
                                    formData.append(
                                        options.paramName[index] || paramName,
                                        file,
                                        file.name
                                    );
                                }
                            });
                        }
                    }
                    options.data = formData;
                }
                // Blob reference is not needed anymore, free memory:
                options.blob = null;
            },
    
            _initIframeSettings: function (options) {
                // Setting the dataType to iframe enables the iframe transport:
                options.dataType = 'iframe ' + (options.dataType || '');
                // The iframe transport accepts a serialized array as form data:
                options.formData = this._getFormData(options);
                // Add redirect url to form data on cross-domain uploads:
                if (options.redirect && $('<a></a>').prop('href', options.url)
                        .prop('host') !== location.host) {
                    options.formData.push({
                        name: options.redirectParamName || 'redirect',
                        value: options.redirect
                    });
                }
            },
    
            _initDataSettings: function (options) {
                if (this._isXHRUpload(options)) {
                    if (!this._chunkedUpload(options, true)) {
                        if (!options.data) {
                            this._initXHRData(options);
                        }
                        this._initProgressListener(options);
                    }
                    if (options.postMessage) {
                        // Setting the dataType to postmessage enables the
                        // postMessage transport:
                        options.dataType = 'postmessage ' + (options.dataType || '');
                    }
                } else {
                    this._initIframeSettings(options, 'iframe');
                }
            },
    
            _getParamName: function (options) {
                var fileInput = $(options.fileInput),
                    paramName = options.paramName;
                if (!paramName) {
                    paramName = [];
                    fileInput.each(function () {
                        var input = $(this),
                            name = input.prop('name') || 'files[]',
                            i = (input.prop('files') || [1]).length;
                        while (i) {
                            paramName.push(name);
                            i -= 1;
                        }
                    });
                    if (!paramName.length) {
                        paramName = [fileInput.prop('name') || 'files[]'];
                    }
                } else if (!$.isArray(paramName)) {
                    paramName = [paramName];
                }
                return paramName;
            },
    
            _initFormSettings: function (options) {
                // Retrieve missing options from the input field and the
                // associated form, if available:
                if (!options.form || !options.form.length) {
                    options.form = $(options.fileInput.prop('form'));
                    // If the given file input doesn't have an associated form,
                    // use the default widget file input's form:
                    if (!options.form.length) {
                        options.form = $(this.options.fileInput.prop('form'));
                    }
                }
                options.paramName = this._getParamName(options);
                // !!!!!!!
                // life change url dynamicly
                //
                if(options.urlFunc) {
                    options.url = options.urlFunc();
                }
                if (!options.url) {
                    options.url = options.form.prop('action') || location.href;
                }
                // The HTTP request method must be "POST" or "PUT":
                options.type = (options.type || options.form.prop('method') || '')
                    .toUpperCase();
                if (options.type !== 'POST' && options.type !== 'PUT' &&
                        options.type !== 'PATCH') {
                    options.type = 'POST';
                }
                if (!options.formAcceptCharset) {
                    options.formAcceptCharset = options.form.attr('accept-charset');
                }
            },
    
            _getAJAXSettings: function (data) {
                var options = $.extend({}, this.options, data);
                this._initFormSettings(options);
                this._initDataSettings(options);
                return options;
            },
    
            // jQuery 1.6 doesn't provide .state(),
            // while jQuery 1.8+ removed .isRejected() and .isResolved():
            _getDeferredState: function (deferred) {
                if (deferred.state) {
                    return deferred.state();
                }
                if (deferred.isResolved()) {
                    return 'resolved';
                }
                if (deferred.isRejected()) {
                    return 'rejected';
                }
                return 'pending';
            },
    
            // Maps jqXHR callbacks to the equivalent
            // methods of the given Promise object:
            _enhancePromise: function (promise) {
                promise.success = promise.done;
                promise.error = promise.fail;
                promise.complete = promise.always;
                return promise;
            },
    
            // Creates and returns a Promise object enhanced with
            // the jqXHR methods abort, success, error and complete:
            _getXHRPromise: function (resolveOrReject, context, args) {
                var dfd = $.Deferred(),
                    promise = dfd.promise();
                context = context || this.options.context || promise;
                if (resolveOrReject === true) {
                    dfd.resolveWith(context, args);
                } else if (resolveOrReject === false) {
                    dfd.rejectWith(context, args);
                }
                promise.abort = dfd.promise;
                return this._enhancePromise(promise);
            },
    
            // Adds convenience methods to the callback arguments:
            _addConvenienceMethods: function (e, data) {
                var that = this;
                data.submit = function () {
                    if (this.state() !== 'pending') {
                        data.jqXHR = this.jqXHR =
                            (that._trigger('submit', e, this) !== false) &&
                            that._onSend(e, this);
                    }
                    return this.jqXHR || that._getXHRPromise();
                };
                data.abort = function () {
                    if (this.jqXHR) {
                        return this.jqXHR.abort();
                    }
                    return this._getXHRPromise();
                };
                data.state = function () {
                    if (this.jqXHR) {
                        return that._getDeferredState(this.jqXHR);
                    }
                };
                data.progress = function () {
                    return this._progress;
                };
            },
    
            // Parses the Range header from the server response
            // and returns the uploaded bytes:
            _getUploadedBytes: function (jqXHR) {
                var range = jqXHR.getResponseHeader('Range'),
                    parts = range && range.split('-'),
                    upperBytesPos = parts && parts.length > 1 &&
                        parseInt(parts[1], 10);
                return upperBytesPos && upperBytesPos + 1;
            },
    
            // Uploads a file in multiple, sequential requests
            // by splitting the file up in multiple blob chunks.
            // If the second parameter is true, only tests if the file
            // should be uploaded in chunks, but does not invoke any
            // upload requests:
            _chunkedUpload: function (options, testOnly) {
                var that = this,
                    file = options.files[0],
                    fs = file.size,
                    ub = options.uploadedBytes = options.uploadedBytes || 0,
                    mcs = options.maxChunkSize || fs,
                    slice = file.slice || file.webkitSlice || file.mozSlice,
                    dfd = $.Deferred(),
                    promise = dfd.promise(),
                    jqXHR,
                    upload;
                if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
                        options.data) {
                    return false;
                }
                if (testOnly) {
                    return true;
                }
                if (ub >= fs) {
                    file.error = 'Uploaded bytes exceed file size';
                    return this._getXHRPromise(
                        false,
                        options.context,
                        [null, 'error', file.error]
                    );
                }
                // The chunk upload method:
                upload = function () {
                    // Clone the options object for each chunk upload:
                    var o = $.extend({}, options),
                        currentLoaded = o._progress.loaded;
                    o.blob = slice.call(
                        file,
                        ub,
                        ub + mcs,
                        file.type
                    );
                    // Store the current chunk size, as the blob itself
                    // will be dereferenced after data processing:
                    o.chunkSize = o.blob.size;
                    // Expose the chunk bytes position range:
                    o.contentRange = 'bytes ' + ub + '-' +
                        (ub + o.chunkSize - 1) + '/' + fs;
                    // Process the upload data (the blob and potential form data):
                    that._initXHRData(o);
                    // Add progress listeners for this chunk upload:
                    that._initProgressListener(o);
                    jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
                            that._getXHRPromise(false, o.context))
                        .done(function (result, textStatus, jqXHR) {
                            ub = that._getUploadedBytes(jqXHR) ||
                                (ub + o.chunkSize);
                            // Create a progress event if no final progress event
                            // with loaded equaling total has been triggered
                            // for this chunk:
                            if (o._progress.loaded === currentLoaded) {
                                that._onProgress($.Event('progress', {
                                    lengthComputable: true,
                                    loaded: ub - o.uploadedBytes,
                                    total: ub - o.uploadedBytes
                                }), o);
                            }
                            options.uploadedBytes = o.uploadedBytes = ub;
                            o.result = result;
                            o.textStatus = textStatus;
                            o.jqXHR = jqXHR;
                            that._trigger('chunkdone', null, o);
                            that._trigger('chunkalways', null, o);
                            if (ub < fs) {
                                // File upload not yet complete,
                                // continue with the next chunk:
                                upload();
                            } else {
                                dfd.resolveWith(
                                    o.context,
                                    [result, textStatus, jqXHR]
                                );
                            }
                        })
                        .fail(function (jqXHR, textStatus, errorThrown) {
                            o.jqXHR = jqXHR;
                            o.textStatus = textStatus;
                            o.errorThrown = errorThrown;
                            that._trigger('chunkfail', null, o);
                            that._trigger('chunkalways', null, o);
                            dfd.rejectWith(
                                o.context,
                                [jqXHR, textStatus, errorThrown]
                            );
                        });
                };
                this._enhancePromise(promise);
                promise.abort = function () {
                    return jqXHR.abort();
                };
                upload();
                return promise;
            },
    
            _beforeSend: function (e, data) {
                if (this._active === 0) {
                    // the start callback is triggered when an upload starts
                    // and no other uploads are currently running,
                    // equivalent to the global ajaxStart event:
                    this._trigger('start');
                    // Set timer for global bitrate progress calculation:
                    this._bitrateTimer = new this._BitrateTimer();
                    // Reset the global progress values:
                    this._progress.loaded = this._progress.total = 0;
                    this._progress.bitrate = 0;
                }
                if (!data._progress) {
                    data._progress = {};
                }
                data._progress.loaded = data.loaded = data.uploadedBytes || 0;
                data._progress.total = data.total = this._getTotal(data.files) || 1;
                data._progress.bitrate = data.bitrate = 0;
                this._active += 1;
                // Initialize the global progress values:
                this._progress.loaded += data.loaded;
                this._progress.total += data.total;
            },
    
            _onDone: function (result, textStatus, jqXHR, options) {
                var total = options._progress.total;
                if (options._progress.loaded < total) {
                    // Create a progress event if no final progress event
                    // with loaded equaling total has been triggered:
                    this._onProgress($.Event('progress', {
                        lengthComputable: true,
                        loaded: total,
                        total: total
                    }), options);
                }
                options.result = result;
                options.textStatus = textStatus;
                options.jqXHR = jqXHR;
                this._trigger('done', null, options);
            },
    
            _onFail: function (jqXHR, textStatus, errorThrown, options) {
                options.jqXHR = jqXHR;
                options.textStatus = textStatus;
                options.errorThrown = errorThrown;
                this._trigger('fail', null, options);
                if (options.recalculateProgress) {
                    // Remove the failed (error or abort) file upload from
                    // the global progress calculation:
                    this._progress.loaded -= options._progress.loaded;
                    this._progress.total -= options._progress.total;
                }
            },
    
            _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
                // jqXHRorResult, textStatus and jqXHRorError are added to the
                // options object via done and fail callbacks
                this._active -= 1;
                this._trigger('always', null, options);
                if (this._active === 0) {
                    // The stop callback is triggered when all uploads have
                    // been completed, equivalent to the global ajaxStop event:
                    this._trigger('stop');
                }
            },
    
            _onSend: function (e, data) {
                if (!data.submit) {
                    this._addConvenienceMethods(e, data);
                }
                var that = this,
                    jqXHR,
                    aborted,
                    slot,
                    pipe,
                    options = that._getAJAXSettings(data),
                    send = function () {
                        that._sending += 1;
                        // Set timer for bitrate progress calculation:
                        options._bitrateTimer = new that._BitrateTimer();
                        jqXHR = jqXHR || (
                            ((aborted || that._trigger('send', e, options) === false) &&
                            that._getXHRPromise(false, options.context, aborted)) ||
                            that._chunkedUpload(options) || $.ajax(options)
                        ).done(function (result, textStatus, jqXHR) {
                            that._onDone(result, textStatus, jqXHR, options);
                        }).fail(function (jqXHR, textStatus, errorThrown) {
                            that._onFail(jqXHR, textStatus, errorThrown, options);
                        }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
                            that._sending -= 1;
                            that._onAlways(
                                jqXHRorResult,
                                textStatus,
                                jqXHRorError,
                                options
                            );
                            if (options.limitConcurrentUploads &&
                                    options.limitConcurrentUploads > that._sending) {
                                // Start the next queued upload,
                                // that has not been aborted:
                                var nextSlot = that._slots.shift();
                                while (nextSlot) {
                                    if (that._getDeferredState(nextSlot) === 'pending') {
                                        nextSlot.resolve();
                                        break;
                                    }
                                    nextSlot = that._slots.shift();
                                }
                            }
                        });
                        return jqXHR;
                    };
                this._beforeSend(e, options);
                if (this.options.sequentialUploads ||
                        (this.options.limitConcurrentUploads &&
                        this.options.limitConcurrentUploads <= this._sending)) {
                    if (this.options.limitConcurrentUploads > 1) {
                        slot = $.Deferred();
                        this._slots.push(slot);
                        pipe = slot.pipe(send);
                    } else {
                        pipe = (this._sequence = this._sequence.pipe(send, send));
                    }
                    // Return the piped Promise object, enhanced with an abort method,
                    // which is delegated to the jqXHR object of the current upload,
                    // and jqXHR callbacks mapped to the equivalent Promise methods:
                    pipe.abort = function () {
                        aborted = [undefined, 'abort', 'abort'];
                        if (!jqXHR) {
                            if (slot) {
                                slot.rejectWith(options.context, aborted);
                            }
                            return send();
                        }
                        return jqXHR.abort();
                    };
                    return this._enhancePromise(pipe);
                }
                return send();
            },
    
            _onAdd: function (e, data) {
                var that = this,
                    result = true,
                    options = $.extend({}, this.options, data),
                    limit = options.limitMultiFileUploads,
                    paramName = this._getParamName(options),
                    paramNameSet,
                    paramNameSlice,
                    fileSet,
                    i;
                if (!(options.singleFileUploads || limit) ||
                        !this._isXHRUpload(options)) {
                    fileSet = [data.files];
                    paramNameSet = [paramName];
                } else if (!options.singleFileUploads && limit) {
                    fileSet = [];
                    paramNameSet = [];
                    for (i = 0; i < data.files.length; i += limit) {
                        fileSet.push(data.files.slice(i, i + limit));
                        paramNameSlice = paramName.slice(i, i + limit);
                        if (!paramNameSlice.length) {
                            paramNameSlice = paramName;
                        }
                        paramNameSet.push(paramNameSlice);
                    }
                } else {
                    paramNameSet = paramName;
                }
                data.originalFiles = data.files;
                $.each(fileSet || data.files, function (index, element) {
                    var newData = $.extend({}, data);
                    newData.files = fileSet ? element : [element];
                    newData.paramName = paramNameSet[index];
                    that._initProgressObject(newData);
                    that._addConvenienceMethods(e, newData);
                    result = that._trigger('add', e, newData);
                    return result;
                });
                return result;
            },
    
            _replaceFileInput: function (input) {
                var inputClone = input.clone(true);
                $('<form></form>').append(inputClone)[0].reset();
                // Detaching allows to insert the fileInput on another form
                // without loosing the file input value:
                input.after(inputClone).detach();
                // Avoid memory leaks with the detached file input:
                $.cleanData(input.unbind('remove'));
                // Replace the original file input element in the fileInput
                // elements set with the clone, which has been copied including
                // event handlers:
                this.options.fileInput = this.options.fileInput.map(function (i, el) {
                    if (el === input[0]) {
                        return inputClone[0];
                    }
                    return el;
                });
                // If the widget has been initialized on the file input itself,
                // override this.element with the file input clone:
                if (input[0] === this.element[0]) {
                    this.element = inputClone;
                }
            },
    
            _handleFileTreeEntry: function (entry, path) {
                var that = this,
                    dfd = $.Deferred(),
                    errorHandler = function (e) {
                        if (e && !e.entry) {
                            e.entry = entry;
                        }
                        // Since $.when returns immediately if one
                        // Deferred is rejected, we use resolve instead.
                        // This allows valid files and invalid items
                        // to be returned together in one set:
                        dfd.resolve([e]);
                    },
                    dirReader;
                path = path || '';
                if (entry.isFile) {
                    if (entry._file) {
                        // Workaround for Chrome bug #149735
                        entry._file.relativePath = path;
                        dfd.resolve(entry._file);
                    } else {
                        entry.file(function (file) {
                            file.relativePath = path;
                            dfd.resolve(file);
                        }, errorHandler);
                    }
                } else if (entry.isDirectory) {
                    dirReader = entry.createReader();
                    dirReader.readEntries(function (entries) {
                        that._handleFileTreeEntries(
                            entries,
                            path + entry.name + '/'
                        ).done(function (files) {
                            dfd.resolve(files);
                        }).fail(errorHandler);
                    }, errorHandler);
                } else {
                    // Return an empy list for file system items
                    // other than files or directories:
                    dfd.resolve([]);
                }
                return dfd.promise();
            },
    
            _handleFileTreeEntries: function (entries, path) {
                var that = this;
                return $.when.apply(
                    $,
                    $.map(entries, function (entry) {
                        return that._handleFileTreeEntry(entry, path);
                    })
                ).pipe(function () {
                    return Array.prototype.concat.apply(
                        [],
                        arguments
                    );
                });
            },
    
            _getDroppedFiles: function (dataTransfer) {
                dataTransfer = dataTransfer || {};
                var items = dataTransfer.items;
                if (items && items.length && (items[0].webkitGetAsEntry ||
                        items[0].getAsEntry)) {
                    return this._handleFileTreeEntries(
                        $.map(items, function (item) {
                            var entry;
                            if (item.webkitGetAsEntry) {
                                entry = item.webkitGetAsEntry();
                                if (entry) {
                                    // Workaround for Chrome bug #149735:
                                    entry._file = item.getAsFile();
                                }
                                return entry;
                            }
                            return item.getAsEntry();
                        })
                    );
                }
                return $.Deferred().resolve(
                    $.makeArray(dataTransfer.files)
                ).promise();
            },
    
            _getSingleFileInputFiles: function (fileInput) {
                fileInput = $(fileInput);
                var entries = fileInput.prop('webkitEntries') ||
                        fileInput.prop('entries'),
                    files,
                    value;
                if (entries && entries.length) {
                    return this._handleFileTreeEntries(entries);
                }
                files = $.makeArray(fileInput.prop('files'));
                if (!files.length) {
                    value = fileInput.prop('value');
                    if (!value) {
                        return $.Deferred().resolve([]).promise();
                    }
                    // If the files property is not available, the browser does not
                    // support the File API and we add a pseudo File object with
                    // the input value as name with path information removed:
                    files = [{name: value.replace(/^.*\\/, '')}];
                } else if (files[0].name === undefined && files[0].fileName) {
                    // File normalization for Safari 4 and Firefox 3:
                    $.each(files, function (index, file) {
                        file.name = file.fileName;
                        file.size = file.fileSize;
                    });
                }
                return $.Deferred().resolve(files).promise();
            },
    
            _getFileInputFiles: function (fileInput) {
                if (!(fileInput instanceof $) || fileInput.length === 1) {
                    return this._getSingleFileInputFiles(fileInput);
                }
                return $.when.apply(
                    $,
                    $.map(fileInput, this._getSingleFileInputFiles)
                ).pipe(function () {
                    return Array.prototype.concat.apply(
                        [],
                        arguments
                    );
                });
            },
    
            _onChange: function (e) {
                var that = this,
                    data = {
                        fileInput: $(e.target),
                        form: $(e.target.form)
                    };
                this._getFileInputFiles(data.fileInput).always(function (files) {
                    data.files = files;
                    if (that.options.replaceFileInput) {
                        that._replaceFileInput(data.fileInput);
                    }
                    if (that._trigger('change', e, data) !== false) {
                        that._onAdd(e, data);
                    }
                });
            },
    
            _onPaste: function (e) {
                var cbd = e.originalEvent.clipboardData,
                    items = (cbd && cbd.items) || [],
                    data = {files: []};
                $.each(items, function (index, item) {
                    var file = item.getAsFile && item.getAsFile();
                    if (file) {
                        data.files.push(file);
                    }
                });
                if (this._trigger('paste', e, data) === false ||
                        this._onAdd(e, data) === false) {
                    return false;
                }
            },
    
            _onDrop: function (e) {
                var that = this,
                    dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
                    data = {};
                if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
                    e.preventDefault();
                }
                this._getDroppedFiles(dataTransfer).always(function (files) {
                    data.files = files;
                    if (that._trigger('drop', e, data) !== false) {
                        that._onAdd(e, data);
                    }
                });
            },
    
            _onDragOver: function (e) {
                var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
                if (this._trigger('dragover', e) === false) {
                    return false;
                }
                if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
                    dataTransfer.dropEffect = 'copy';
                    e.preventDefault();
                }
            },
    
            _initEventHandlers: function () {
                if (this._isXHRUpload(this.options)) {
                    this._on(this.options.dropZone, {
                        dragover: this._onDragOver,
                        drop: this._onDrop
                    });
                    this._on(this.options.pasteZone, {
                        paste: this._onPaste
                    });
                }
                this._on(this.options.fileInput, {
                    change: this._onChange
                });
            },
    
            _destroyEventHandlers: function () {
                this._off(this.options.dropZone, 'dragover drop');
                this._off(this.options.pasteZone, 'paste');
                this._off(this.options.fileInput, 'change');
            },
    
            _setOption: function (key, value) {
                var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
                if (refresh) {
                    this._destroyEventHandlers();
                }
                this._super(key, value);
                if (refresh) {
                    this._initSpecialOptions();
                    this._initEventHandlers();
                }
            },
    
            _initSpecialOptions: function () {
                var options = this.options;
                if (options.fileInput === undefined) {
                    options.fileInput = this.element.is('input[type="file"]') ?
                            this.element : this.element.find('input[type="file"]');
                } else if (!(options.fileInput instanceof $)) {
                    options.fileInput = $(options.fileInput);
                }
                if (!(options.dropZone instanceof $)) {
                    options.dropZone = $(options.dropZone);
                }
                if (!(options.pasteZone instanceof $)) {
                    options.pasteZone = $(options.pasteZone);
                }
            },
    
            _create: function () {
                var options = this.options;
                // Initialize options set via HTML5 data-attributes:
                $.extend(options, $(this.element[0].cloneNode(false)).data());
                this._initSpecialOptions();
                this._slots = [];
                this._sequence = this._getXHRPromise(true);
                this._sending = this._active = 0;
                this._initProgressObject(this);
                this._initEventHandlers();
            },
    
            // This method is exposed to the widget API and allows to query
            // the widget upload progress.
            // It returns an object with loaded, total and bitrate properties
            // for the running uploads:
            progress: function () {
                return this._progress;
            },
    
            // This method is exposed to the widget API and allows adding files
            // using the fileupload API. The data parameter accepts an object which
            // must have a files property and can contain additional options:
            // .fileupload('add', {files: filesList});
            add: function (data) {
                var that = this;
                if (!data || this.options.disabled) {
                    return;
                }
                if (data.fileInput && !data.files) {
                    this._getFileInputFiles(data.fileInput).always(function (files) {
                        data.files = files;
                        that._onAdd(null, data);
                    });
                } else {
                    data.files = $.makeArray(data.files);
                    this._onAdd(null, data);
                }
            },
    
            // This method is exposed to the widget API and allows sending files
            // using the fileupload API. The data parameter accepts an object which
            // must have a files or fileInput property and can contain additional options:
            // .fileupload('send', {files: filesList});
            // The method returns a Promise object for the file upload call.
            send: function (data) {
                if (data && !this.options.disabled) {
                    if (data.fileInput && !data.files) {
                        var that = this,
                            dfd = $.Deferred(),
                            promise = dfd.promise(),
                            jqXHR,
                            aborted;
                        promise.abort = function () {
                            aborted = true;
                            if (jqXHR) {
                                return jqXHR.abort();
                            }
                            dfd.reject(null, 'abort', 'abort');
                            return promise;
                        };
                        this._getFileInputFiles(data.fileInput).always(
                            function (files) {
                                if (aborted) {
                                    return;
                                }
                                data.files = files;
                                jqXHR = that._onSend(null, data).then(
                                    function (result, textStatus, jqXHR) {
                                        dfd.resolve(result, textStatus, jqXHR);
                                    },
                                    function (jqXHR, textStatus, errorThrown) {
                                        dfd.reject(jqXHR, textStatus, errorThrown);
                                    }
                                );
                            }
                        );
                        return this._enhancePromise(promise);
                    }
                    data.files = $.makeArray(data.files);
                    if (data.files.length) {
                        return this._onSend(null, data);
                    }
                }
                return this._getXHRPromise(false, data && data.context);
            }
    
        });
    
    }));
    
    define("fileupload", ["jquery.ui.widget","iframe-transport"], function(){});
    
    // for editor.
    // drag image to editor
    var urlPrefix = UrlPrefix; // window.location.protocol + "//" + window.location.host;
    define('editor_drop_paste', ['jquery.ui.widget', 'fileupload'], function(){
    	function Process(editor) {
    		var id = '__mcenew' + (new Date()).getTime();
    		var str = '<div contenteditable="false" id="' + id + '" class="leanote-image-container">' + 
    			'<img class="loader" src="/images/ajax-loader.gif">' + 
    				'<div class="progress">' + 
    					'<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="2" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">' + 
    						'0%' + 
    					'</div>' + 
    				'</div>' + 
    			'</div>';
    		this.containerStr = str;
    		editor.insertContent(str);
    		var container = $('#' + id);
    		this.container = container;
    		this.id = id;
    		this.processBar = container.find('.progress-bar');
    	}
    	Process.prototype.update = function(process) {
    		var me = this;
    		// 98%, 不要小数
    		process = Math.ceil(process * 100);
    		if(process >= 100) {
    			process = 99;
    		}
    		process += "%";
    		$('#' + me.id + ' .progress-bar').html(process).css('width', process);
    	}
    	Process.prototype.replace = function(src) {
    		var me = this;
    		getImageSize(src, function() {
    			$('#' + me.id).replaceWith('<img src="' + src + '" />');
    		});
    	}
    	Process.prototype.remove = function() {
    		var me = this;
    		$('#' + me.id).remove();
    	}
    
    	// 当url改变时, 得到图片的大小
    	function getImageSize(url, callback) {
    		var img = document.createElement('img');
    	
    		function done(width, height) {
    			img.parentNode.removeChild(img);
    			callback({width: width, height: height});
    		}
    	
    		img.onload = function() {
    			done(img.clientWidth, img.clientHeight);
    		};
    	
    		img.onerror = function() {
    			done();
    		};
    	
    		img.src = url;
    	
    		var style = img.style;
    		style.visibility = 'hidden';
    		style.position = 'fixed';
    		style.bottom = style.left = 0;
    		style.width = style.height = 'auto';
    	
    		document.body.appendChild(img);
    	}
    	
    	var i = 1;
    	function insertImage(data) {
    		var editor = tinymce.activeEditor;
    		var dom = editor.dom;
    	
    		var renderImage = function(data2) {
    			// 这里, 如果图片宽度过大, 这里设置成500px
    			var d = {};
    			var imgElm;
    			// 先显示loading...
    			d.id = '__mcenew' + (i++);
    			d.src = "http://leanote.com/images/loading-24.gif";
    			imgElm = dom.createHTML('img', d);
    			tinymce.activeEditor.insertContent(imgElm);
    			imgElm = dom.get(d.id);
    			
    			function callback (wh) {
    				dom.setAttrib(imgElm, 'src', data2.src);
    				// dom.setAttrib(imgElm, 'width', data2.width);
    				if(data2.title) {
    					dom.setAttrib(imgElm, 'title', data2.title);
    				}
    				
    				dom.setAttrib(imgElm, 'id', null);
    			};
    			getImageSize(data.src, callback);
    		}
    		
    		//-------------
    		// outputImage?fileId=123232323
    		var fileId = "";
    		fileIds = data.src.split("fileId=")
    		if(fileIds.length == 2 && fileIds[1].length == "53aecf8a8a039a43c8036282".length) {
    			fileId = fileIds[1];
    		}
    		if(fileId) {
    			// 得到fileId, 如果这个笔记不是我的, 那么肯定是协作的笔记, 那么需要将图片copy给原note owner
    			var curNote = Note.getCurNote();
    			if(curNote && curNote.UserId != UserInfo.UserId) {
    				(function(data) {
    					ajaxPost("/file/copyImage", {userId: UserInfo.UserId, fileId: fileId, toUserId: curNote.UserId}, function(re) {
    						if(reIsOk(re) && re.Id) {
    							var urlPrefix = window.location.protocol + "//" + window.location.host;
    							data.src = urlPrefix + "/file/outputImage?fileId=" + re.Id;
    						}
    						renderImage(data);
    					});
    				})(data);
    			} else {
    				renderImage(data);
    			}
    		} else {
    			renderImage(data);
    		}
    	}
    	
    	var initUploader =  function() {
    		var ul = $('#upload ul');
    	
    	    $('#drop a').click(function() {
    	        // trigger to show file select
    	        $(this).parent().find('input').click();
    	    });
    	
    	    // Initialize the jQuery File Upload plugin
    	    $('#upload').fileupload({
    	        dataType: 'json',
    	        pasteZone: '', // 不允许paste
    	        acceptFileTypes: /(\.|\/)(gif|jpg|jpeg|png|jpe)$/i,
    	        maxFileSize: 210000,
    	
    	        // This element will accept file drag/drop uploading
    	        dropZone: $('#drop'),
    	        formData: function(form) {
    	        	return [{name: 'albumId', value: ""}]
    	        },
    	        // This function is called when a file is added to the queue;
    	        // either via the browse button, or via drag/drop:
    	        add: function(e, data) {
    	            var tpl = $('<li><div class="alert alert-info"><img class="loader" src="/tinymce/plugins/leaui_image/public/images/ajax-loader.gif"> <a class="close" data-dismiss="alert">×</a></div></li>');
    	
    	            // Append the file name and file size
    	            tpl.find('div').append(data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small>');
    	
    	            // Add the HTML to the UL element
    	            data.context = tpl.appendTo(ul);
    	
    	            // data.form[0].action += "&album_id=" + $("#albumsForUpload").val();
    	
    	            // Automatically upload the file once it is added to the queue
    	            var jqXHR = data.submit();
    	        },
    	
    	        done: function(e, data) {
    	            if (data.result.Ok == true) {
    	                data.context.remove();
    	                // life
    	                var data2 = {src: urlPrefix + "/file/outputImage?fileId=" + data.result.Id}
    	                insertImage(data2);
    	            } else {
    	                data.context.empty();
    	                var tpl = $('<li><div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div></li>');
    	                tpl.find('div').append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.result.Msg);
    	                data.context.append(tpl);
    	                setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 2000);
    	            }
    	            $("#uploadMsg").scrollTop(1000);
    	        },
    	        fail: function(e, data) {
    	            data.context.empty();
    	            var tpl = $('<li><div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div></li>');
    	            tpl.find('div').append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.errorThrown);
    	            data.context.append(tpl);
    	            setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 2000);
    	
    	            $("#uploadMsg").scrollTop(1000);
    	        }
    	    });
    	
    	    // Prevent the default action when a file is dropped on the window
    	    $(document).on('drop dragover', function(e) {
    	        e.preventDefault();
    	    });
    	
    	    // Helper function that formats the file sizes
    	    function formatFileSize(bytes) {
    	        if (typeof bytes !== 'number') {
    	            return '';
    	        }
    	        if (bytes >= 1000000000) {
    	            return (bytes / 1000000000).toFixed(2) + ' GB';
    	        }
    	        if (bytes >= 1000000) {
    	            return (bytes / 1000000).toFixed(2) + ' MB';
    	        }
    	        return (bytes / 1000).toFixed(2) + ' KB';
    	    }
    	    
    	    function showUpload() {
    	    	$("#upload").css("z-index", 12);
    	    	var top = +$("#mceToolbar").css("height").slice(0, -2); // px
    	    	$("#upload").css("top", top - 8);
    	    	$("#upload").show();
    	    }
    	    
    	    function hideUpload() {
    	    	$("#upload").css("z-index", 0).css("top", "auto").hide();
    	    }
    	    
    	    // drag css
    		$(document).bind('dragover', function (e) {
    		    var dropZone = $('#drop'),
    		        timeout = window.dropZoneTimeout;
    		    if (!timeout) {
    		        dropZone.addClass('in');
    		        showUpload();
    		    } else {
    		        clearTimeout(timeout);
    		    }
    		    
    		    var found = false,
    		        node = e.target;
    		    do {
    		        if (node === dropZone[0]) {
    		            found = true;
    		            break;
    		        }
    		        node = node.parentNode;
    		    } while (node != null);
    		    if (found) {
    		        dropZone.addClass('hover');
    		    } else {
    		        dropZone.removeClass('hover');
    		    }
    		    window.dropZoneTimeout = setTimeout(function () {
    		        window.dropZoneTimeout = null;
    		        dropZone.removeClass('in hover');
    		        hideUpload();
    		    }, 100);
    		});
    	};
    
    
    	// pasteImage
    	var pasteImageInit =  function() {
    	    // Initialize the jQuery File Upload plugin
    	    var dom, editor;
    	    $('#editorContent').fileupload({
    	        dataType: 'json',
    	        pasteZone: $('#editorContent'),
    	        dropZone: '', // 只允许paste
    	        maxFileSize: 210000,
    	        url: "/file/pasteImage",
    	        paramName: 'file',
    	        formData: function(form) {
    	        	return [{name: 'from', value: 'pasteImage'}, {name: 'noteId', value: Note.curNoteId}]
    	        },
    	        /*
    	        paste: function(e, data) {
    	        	var jqXHR = data.submit();
    	        },
    	        */
    	        progress: function(e, data) {
    	        	data.process.update(data.loaded / data.total);
    	        },
    	        add: function(e, data) {
    	        	var note = Note.getCurNote();
    	        	if(!note || note.IsNew) {
    	        		alert("This note hasn't saved, please save it firstly!")
    	        		return;
    	        	}
    	        	// 先显示loading...
    				editor = tinymce.EditorManager.activeEditor; 
    				var process = new Process(editor);
    				data.process = process;
    	            var jqXHR = data.submit();
    				/*
    				d.id = '__mcenew' + (new Date()).getTime();
    				d.src = "http://leanote.com/images/loading-24.gif"; // 写死了
    				var img = '<img src="' + d.src + '" id="' + d.id + '" />';
    				editor.insertContent(img);
    				var imgElm = $(d.id);
    				data.imgId = d.id;
    				data.context = imgElm;
    				*/
    
    	        	/*
    	        	// 上传之
    		      	var c = new FormData;
    			    c.append("from", "pasteImage");
    			    // var d;
    			    // d = $.ajaxSettings.xhr();
    			    // d.withCredentials = i;var d = {};
    			    
    				// 先显示loading...
    				var editor = tinymce.EditorManager.activeEditor; 
    				var dom = editor.dom;
    				var d = {};						
    				d.id = '__mcenew';
    				d.src = "http://leanote.com/images/loading-24.gif"; // 写死了
    				editor.insertContent(dom.createHTML('img', d));
    				var imgElm = dom.get('__mcenew');
    			    $.ajax({url: "/file/pasteImage", contentType:false, processData:false , data: c, type: "POST"}
    			    	).done(function(re) {
    			    		if(!re || typeof re != "object" || !re.Ok) {
    			    			// 删除
    			    			dom.remove(imgElm);
    			    			return;
    			    		}
    			    		// 这里, 如果图片宽度过大, 这里设置成500px
    						var urlPrefix = UrlPrefix; // window.location.protocol + "//" + window.location.host;
    						var src = urlPrefix + "/file/outputImage?fileId=" + re.Id;
    						getImageSize(src, function(wh) {
    							// life 4/25
    							if(wh && wh.width) {
    								if(wh.width > 600) {
    									wh.width = 600;
    								}
    								d.width = wh.width;
    								dom.setAttrib(imgElm, 'width', d.width);
    							}
    							dom.setAttrib(imgElm, 'src', src);
    						});
    						dom.setAttrib(imgElm, 'id', null);
    			    	});
    			    };
    			    reader.readAsDataURL(blob);
    			    */
    	        },
    	
    	        done: function(e, data) {
    	            if (data.result.Ok == true) {
    		    		// 这里, 如果图片宽度过大, 这里设置成500px
    		    		var re = data.result;
    					var urlPrefix = UrlPrefix; // window.location.protocol + "//" + window.location.host;
    					var src = urlPrefix + "/file/outputImage?fileId=" + re.Id;
    
    					data.process.replace(src);
    					/*
    					getImageSize(src, function() {
    						$img.attr('src', src);
    						$img.removeAttr('id');
    					});
    					*/
    	            } else {
    					data.process.remove();
    	            }
    	        },
    	        fail: function(e, data) {
    				data.process.remove();
    	        }
    	    });
    	};
    	
    	initUploader();
    	pasteImageInit();
    });
    // upload attachment
    // 依赖note
    var urlPrefix = UrlPrefix;
    define('attachment_upload', ['jquery.ui.widget', 'fileupload'], function(){
    	// Helper function that formats the file sizes
        function formatFileSize(bytes) {
            if (typeof bytes !== 'number') {
                return '';
            }
            if (bytes >= 1000000000) {
                return (bytes / 1000000000).toFixed(2) + ' GB';
            }
            if (bytes >= 1000000) {
                return (bytes / 1000000).toFixed(2) + ' MB';
            }
            return (bytes / 1000).toFixed(2) + ' KB';
        }
        
        function setDropStyle(dropzoneId, formId) {
    	    // drag css
    	    var dropZone = $(dropzoneId);
    		$(formId).bind('dragover', function (e) {
    			e.preventDefault();
    		    var timeout = window.dropZoneTimeoutAttach;
    		    if(timeout) {
    		        clearTimeout(timeout);
    		    }
    		    
    		    var found = false,
    		        node = e.target;
    		    do {
    		        if (node === dropZone[0]) {
    		            found = true;
    		            break;
    		        }
    		        node = node.parentNode;
    		    } while (node != null);
    		    if (found) {
    		        dropZone.addClass('hover');
    		    } else {
    		        dropZone.removeClass('hover');
    		    }
    		    window.dropZoneTimeoutAttach = setTimeout(function () {
    		        window.dropZoneTimeoutAttach = null;
    		        dropZone.removeClass('in hover');
    		    }, 100);
    		});
        }
        
        setDropStyle("#dropAttach", "#uploadAttach");
        setDropStyle("#dropAvatar", "#uploadAvatar");
        
    	var initUploader = function() {
    	    $('.dropzone .btn-choose-file').click(function() {
    	        $(this).parent().find('input').click();
    	    });
    
    		var $msg = $('#attachUploadMsg');
    	    // Initialize the jQuery File Upload plugin
    	    $('#uploadAttach').fileupload({
    	        dataType: 'json',
    	        pasteZone: '', // 不能通过paste来上传图片
    	        // This element will accept file drag/drop uploading
    	        dropZone: $('#dropAttach'),
    	        formData: function(form) {
    	        	return [{name: 'noteId', value: Note.curNoteId}] // 传递笔记本过去
    	        },
    	        // This function is called when a file is added to the queue;
    	        // either via the browse button, or via drag/drop:
    	        add: function(e, data) {
    	        	var note = Note.getCurNote();
    	        	if(!note || note.IsNew) {
    	        		alert("This note hasn't saved, please save it firstly!")
    	        		return;
    	        	}
    
    	            var tpl = $('<div class="alert alert-info"><img class="loader" src="/tinymce/plugins/leaui_image/public/images/ajax-loader.gif"> <a class="close" data-dismiss="alert">×</a></div>');
    	
    	            // Append the file name and file size
    	            tpl.append(data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small>');
    	
    	            // Add the HTML to the UL element
    	            $msg.html(tpl);
    	            data.context = $msg;
    	            
    	            // 检查文件大小
    	            var size = data.files[0].size;
    	            var maxFileSize = +GlobalConfigs["uploadAttachSize"] || 100;
    	            if(typeof size == 'number' && size > 1024 * 1024 * maxFileSize) {
    	            	tpl.find("img").remove();
    	            	tpl.removeClass("alert-info").addClass("alert-danger");
    	            	tpl.append(" Warning: File size is bigger than " + maxFileSize + "M");
    	            	setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 3000);
    	            	return;
    	            }
    	            
    	            // Automatically upload the file once it is added to the queue
    	            var jqXHR;
    	            setTimeout(function() {
    		            jqXHR = data.submit();
    	            }, 10);
    	        },
    	        /*
    	        progress: function (e, data) {
    	        },
    	        */
    	        done: function(e, data) {
    	            if (data.result.Ok == true) {
    	                data.context.html("");
    	                Attach.addAttach(data.result.Item);
    	            } else {
    	                var re = data.result;
    	                data.context.html("");
    	                var tpl = $('<div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div>');
    	                tpl.append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.result.Msg);
    	                data.context.html(tpl);
    	                setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 3000);
    	            }
    	            $("#uploadAttachMsg").scrollTop(1000);
    	        },
    	        fail: function(e, data) {
                    data.context.html("");
    	            var tpl = $('<div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div>');
    	            tpl.append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.errorThrown);
    	            data.context.html(tpl);
    	            setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 3000);
    	
    	            $("#uploadAttachMsg").scrollTop(1000);
    	        }
    	    });
    	    
    	    //-------------------
    	    // 已经过时, 没有avatar了
    	    
    	    var $msg2 = $('#avatarUploadMsg');
    	    $('#uploadAvatar').fileupload({
    	        dataType: 'json',
    	        dropZone: $('#dropAvatar'),
    	        pasteZone: '',
    	        add: function(e, data) {
    	            var tpl = $('<div class="alert alert-info"><img class="loader" src="/tinymce/plugins/leaui_image/public/images/ajax-loader.gif"> <a class="close" data-dismiss="alert">×</a></div>');
    	
    	            // Append the file name and file size
    	            tpl.append(data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small>');
    	
    	            // Add the HTML to the UL element
    	            $msg2.html(tpl);
    	            data.context = $msg2;
    	            
    	            // 检查文件大小
    	            var size = data.files[0].size;
    	            var maxFileSize = +GlobalConfigs["uploadAvatarSize"] || 100;
    	            if(typeof size == 'number' && size > 1024 * 1024 * maxFileSize) {
    	            	tpl.find("img").remove();
    	            	tpl.removeClass("alert-info").addClass("alert-danger");
    	            	tpl.append(" Warning: File size is bigger than " + maxFileSize + "M");
    	            	setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 3000);
    	            	return;
    	            }
    	            
    	            // Automatically upload the file once it is added to the queue
    	            var jqXHR;
    	            setTimeout(function() {
    		            jqXHR = data.submit();
    	            }, 10);
    	        },
    	        done: function(e, data) {
    	            if (data.result.Ok == true) {
    	                data.context.html("");
    	                var re = data.result;
    	                $("#avatar").attr("src", UrlPrefix + "/" + re.Id);
    	            } else {
    	                var re = data.result;
    	                data.context.html("");
    	                var tpl = $('<div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div>');
    	                tpl.append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.result.Msg);
    	                data.context.html(tpl);
    	                setTimeout((function(tpl) {
    	                	return function() {
    		                	tpl.remove();
    	                	}
    	                })(tpl), 3000);
    	            }
    	        },
    	        fail: function(e, data) {
                    data.context.html("");
    	            var tpl = $('<div class="alert alert-danger"><a class="close" data-dismiss="alert">×</a></div>');
    	            tpl.append('<b>Error:</b> ' + data.files[0].name + ' <small>[<i>' + formatFileSize(data.files[0].size) + '</i>]</small> ' + data.errorThrown);
    	            data.context.html(tpl);
    	            setTimeout((function(tpl) {
                    	return function() {
    	                	tpl.remove();
                    	}
    	             })(tpl), 3000);
    	        }
    	    });
    	};
    
    	initUploader();
    });
    // RequireJS configuration
    /*global requirejs */
    requirejs.config({
    	waitSeconds: 0,
    	packages: [
    		{
    			name: 'css',
    			location: 'bower-libs/require-css',
    			main: 'css'
    		},
    		{
    			name: 'less',
    			location: 'bower-libs/require-less',
    			main: 'less'
    		}
    	],
    	paths: {
    		// jquery: 'bower-libs/jquery/jquery',
    		underscore: 'bower-libs/underscore/underscore',
    		crel: 'bower-libs/crel/crel',
    		jgrowl: 'bower-libs/jgrowl/jquery.jgrowl',
    		mousetrap: 'bower-libs/mousetrap/mousetrap',
    		'mousetrap-record': 'bower-libs/mousetrap/plugins/record/mousetrap-record',
    		toMarkdown: 'bower-libs/to-markdown/src/to-markdown',
    		text: 'bower-libs/requirejs-text/text',
    		mathjax: 'public/libs/MathJax/MathJax.js?config=TeX-AMS_HTML',
    		bootstrap: 'bower-libs/bootstrap/dist/js/bootstrap',
    		requirejs: 'bower-libs/requirejs/require',
    		'google-code-prettify': 'bower-libs/google-code-prettify/src/prettify',
    		highlightjs: 'libs/highlight/highlight.pack',
    		'jquery-waitforimages': 'bower-libs/waitForImages/src/jquery.waitforimages',
    		'jquery-ui': 'bower-libs/jquery-ui/ui/jquery-ui',
    		'jquery-ui-core': 'bower-libs/jquery-ui/ui/jquery.ui.core',
    		'jquery-ui-widget': 'bower-libs/jquery-ui/ui/jquery.ui.widget',
    		'jquery-ui-mouse': 'bower-libs/jquery-ui/ui/jquery.ui.mouse',
    		'jquery-ui-draggable': 'bower-libs/jquery-ui/ui/jquery.ui.draggable',
    		'jquery-ui-effect': 'bower-libs/jquery-ui/ui/jquery.ui.effect',
    		'jquery-ui-effect-slide': 'bower-libs/jquery-ui/ui/jquery.ui.effect-slide',
    		// FileSaver: 'bower-libs/FileSaver/FileSaver',
    		stacktrace: 'bower-libs/stacktrace/stacktrace',
    		'requirejs-text': 'bower-libs/requirejs-text/text',
    		'bootstrap-tour': 'bower-libs/bootstrap-tour/build/js/bootstrap-tour',
    		css_browser_selector: 'bower-libs/css_browser_selector/css_browser_selector',
    		'pagedown-extra': 'bower-libs/pagedown-extra/node-pagedown-extra',
    		pagedownExtra: 'bower-libs/pagedown-extra/Markdown.Extra',
    		pagedown: 'libs/Markdown.Editor',
    		'require-css': 'bower-libs/require-css/css',
    		xregexp: 'bower-libs/xregexp/xregexp-all',
    		yaml: 'bower-libs/yaml.js/bin/yaml',
    		'yaml.js': 'bower-libs/yaml.js',
    		'yaml-js': 'bower-libs/yaml.js/bin/yaml',
    		css: 'bower-libs/require-css/css',
    		'css-builder': 'bower-libs/require-css/css-builder',
    		normalize: 'bower-libs/require-css/normalize',
    		prism: 'bower-libs/prism/prism',
    		'prism-core': 'bower-libs/prism/components/prism-core',
    		MutationObservers: 'bower-libs/MutationObservers/MutationObserver',
    		WeakMap: 'bower-libs/WeakMap/weakmap',
    		rangy: 'bower-libs/rangy/rangy-core',
    		'rangy-cssclassapplier': 'bower-libs/rangy/rangy-cssclassapplier',
    		diff_match_patch: 'bower-libs/google-diff-match-patch-js/diff_match_patch',
    		diff_match_patch_uncompressed: 'bower-libs/google-diff-match-patch-js/diff_match_patch_uncompressed',
    		jsondiffpatch: 'bower-libs/jsondiffpatch/build/bundle',
    		hammerjs: 'bower-libs/hammerjs/hammer',
    		Diagram: 'bower-libs/js-sequence-diagrams/src/sequence-diagram',
    		'diagram-grammar': 'bower-libs/js-sequence-diagrams/build/diagram-grammar',
    		raphael: 'bower-libs/raphael/raphael',
    		'flow-chart': 'bower-libs/flowchart/release/flowchart.amd-1.3.4.min',
    		flowchart: 'bower-libs/flowchart/release/flowchart-1.3.4.min',
    		monetizejs: 'bower-libs/monetizejs/src/monetize',
    		'to-markdown': 'bower-libs/to-markdown/src/to-markdown',
    		waitForImages: 'bower-libs/waitForImages/dist/jquery.waitforimages',
    		MathJax: '../libs/MathJax/MathJax',
    		alertify: 'bower-libs/alertify.js/lib/alertify',
    		// life
    		'editor_drop_paste': 'app/editor_drop_paste',
        	'attachment_upload': 'app/attachment_upload',
        	'jquery.ui.widget': 'app/libs/jquery.ui.widget',
        	'fileupload': 'app/libs/jquery.fileupload',
        	'iframe-transport': 'app/libs/jquery.iframe-transport',
    	},
    	shim: {
    		underscore: {
    			exports: '_'
    		},
    		mathjax: [
    			'libs/mathjax_init'
    		],
    		jgrowl: {
    			deps: [
    				
    			],
    			exports: 'jQuery.jGrowl'
    		},
    		diff_match_patch_uncompressed: {
    			exports: 'diff_match_patch'
    		},
    		jsondiffpatch: [
    			'diff_match_patch_uncompressed'
    		],
    		rangy: {
    			exports: 'rangy'
    		},
    		'rangy-cssclassapplier': [
    			'rangy'
    		],
    		mousetrap: {
    			exports: 'Mousetrap'
    		},
    		'yaml-js': {
    			exports: 'YAML'
    		},
    		'prism-core': {
    			exports: 'Prism'
    		},
    		'bower-libs/prism/components/prism-markup': [
    			'prism-core'
    		],
    		'libs/prism-latex': [
    			'prism-core'
    		],
    		'libs/prism-markdown': [
    			'bower-libs/prism/components/prism-markup',
    			'libs/prism-latex'
    		],
    		'bootstrap-record': [
    			'mousetrap'
    		],
    		toMarkdown: {
    			deps: [
    				
    			],
    			exports: 'toMarkdown'
    		},
    		stacktrace: {
    			exports: 'printStackTrace'
    		},
    		// FileSaver: {
    			// exports: 'saveAs'
    		// },
    		MutationObservers: [
    			'WeakMap'
    		],
    		highlightjs: {
    			exports: 'hljs'
    		},
    		'bootstrap-tour': {
    			deps: [
    				'bootstrap'
    			],
    			exports: 'Tour'
    		},
    		bootstrap: [
    			
    		],
    		'jquery-waitforimages': [
    			
    		],
    		pagedown: [
    			'libs/Markdown.Converter'
    		],
    		pagedownExtra: [
    			'libs/Markdown.Converter'
    		],
    		'flow-chart': [
    			'raphael'
    		],
    		'diagram-grammar': [
    			'underscore'
    		],
    		Diagram: [
    			'raphael',
    			'diagram-grammar'
    		],
    		// life
        	'fileupload': {deps: ['jquery.ui.widget', 'iframe-transport']},
    	}
    });
    
    window.viewerMode = false;
    // Keep the theme in a global variable
    window.__theme = 'default';
    window.baseDir = ''; // life
    var themeModule = "less!themes/" + window.__theme;
    if(window.baseDir.indexOf('-min') !== -1) {
    	themeModule = "css!themes/" + window.__theme;
    }
    
    // RequireJS entry point. By requiring synchronizer, publisher, sharing and
    // media-importer, we are actually loading all the modules
    require([
    	// "jquery",
    	"rangy",
    	"core",
    	"eventMgr",
    	// "synchronizer",
    	// "publisher",
    	// "sharing",
    	// "mediaImporter",
    	"css",
    	"rangy-cssclassapplier",
    	// "editor_drop_paste",
    	// "attachment_upload"
    	// ,themeModule // 生产模式
    ], function( rangy, core) {
    	if(window.noStart) {
    		return;
    	}
    	$(function() {
    		rangy.init();
    		// Here, all the modules are loaded and the DOM is ready
    		core.onReady();
    	});
    });
    
    define("main", function(){});