From 026675b438cda14ed3c44381dfecd6ec4c95d0c1 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 23 Mar 2018 01:55:47 +0100 Subject: [PATCH] separate dom binding --- examples/html-editor/index.js | 2 +- package-lock.json | 1240 ++++++++++++++++- package.json | 8 +- rollup.browser.js | 4 +- src/Bindings/DomBinding/DomBinding.js | 189 +-- .../DomBinding/applyChangesFromDom.js | 75 - src/Bindings/DomBinding/domObserver.js | 132 ++ src/Bindings/DomBinding/domToType.js | 32 + src/Bindings/DomBinding/filter.js | 31 + .../YXml => Bindings/DomBinding}/selection.js | 24 +- src/Bindings/DomBinding/typeObserver.js | 61 + src/Bindings/DomBinding/util.js | 46 +- src/Bindings/QuillBinding/QuillBinding.js | 25 +- .../TextareaBinding/TextareaBinding.js | 6 +- src/Types/YArray/YArray.js | 1 + src/Types/YXml/YXmlElement.js | 90 +- src/Types/YXml/YXmlEvent.js | 3 +- src/Types/YXml/YXmlFragment.js | 278 +--- src/Types/YXml/YXmlHook.js | 39 +- src/Types/YXml/YXmlText.js | 106 +- src/Types/YXml/domFilter.js | 51 - src/Types/YXml/utils.js | 189 --- src/Util/isParentOf.js | 18 + src/Y.dist.js | 6 +- src/Y.js | 10 +- test/y-xml.tests.js | 134 +- tests-lib/helper.js | 39 +- tests-lib/test-connector.js | 175 ++- 28 files changed, 1802 insertions(+), 1212 deletions(-) delete mode 100644 src/Bindings/DomBinding/applyChangesFromDom.js create mode 100644 src/Bindings/DomBinding/domObserver.js create mode 100644 src/Bindings/DomBinding/domToType.js create mode 100644 src/Bindings/DomBinding/filter.js rename src/{Types/YXml => Bindings/DomBinding}/selection.js (70%) create mode 100644 src/Bindings/DomBinding/typeObserver.js delete mode 100644 src/Types/YXml/domFilter.js delete mode 100644 src/Types/YXml/utils.js create mode 100644 src/Util/isParentOf.js diff --git a/examples/html-editor/index.js b/examples/html-editor/index.js index 946fdb3a..80623031 100644 --- a/examples/html-editor/index.js +++ b/examples/html-editor/index.js @@ -1,7 +1,7 @@ /* global Y */ window.onload = function () { - window.yXmlType.bindToDom(document.body) + window.domBinding = new Y.DomBinding(window.yXmlType, document.body) } let y = new Y('htmleditor', { diff --git a/package-lock.json b/package-lock.json index cdd94b4b..33bed4c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "dev": true, "optional": true }, "accepts": { @@ -30,6 +31,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", + "dev": true, "optional": true, "requires": { "acorn": "2.7.0" @@ -39,6 +41,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, "optional": true } } @@ -96,12 +99,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true }, "anymatch": { "version": "1.3.0", @@ -205,12 +210,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, "optional": true }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "async-array-reduce": { "version": "0.2.1", @@ -228,18 +235,21 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, "optional": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, "optional": true }, "aws4": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, "optional": true }, "babel-cli": { @@ -460,6 +470,7 @@ "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, "requires": { "babel-runtime": "6.23.0" } @@ -876,6 +887,7 @@ "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", + "dev": true, "requires": { "core-js": "2.4.1", "regenerator-runtime": "0.10.5" @@ -915,6 +927,7 @@ "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "dev": true, "requires": { "babel-runtime": "6.23.0", "esutils": "2.0.2", @@ -950,6 +963,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -970,12 +984,14 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true }, "boom": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, "optional": true, "requires": { "hoek": "4.2.1" @@ -1068,6 +1084,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, "optional": true }, "center-align": { @@ -1092,6 +1109,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, "requires": { "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", @@ -1110,6 +1128,7 @@ "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dev": true, "requires": { "css-select": "1.2.0", "dom-serializer": "0.1.0", @@ -1137,6 +1156,7 @@ "requires": { "anymatch": "1.3.0", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1186,7 +1206,8 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "code-point-at": { "version": "1.1.0", @@ -1197,12 +1218,14 @@ "color-logger": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/color-logger/-/color-logger-0.0.3.tgz", - "integrity": "sha1-2bIt0dlz4Waxi/MT+fSBu6TfIBg=" + "integrity": "sha1-2bIt0dlz4Waxi/MT+fSBu6TfIBg=", + "dev": true }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -1361,17 +1384,20 @@ "core-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", - "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, "optional": true, "requires": { "boom": "5.2.0" @@ -1381,6 +1407,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, "optional": true, "requires": { "hoek": "4.2.1" @@ -1392,6 +1419,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, "requires": { "boolbase": "1.0.0", "css-what": "2.1.0", @@ -1402,17 +1430,20 @@ "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true }, "cssom": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=" + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "dev": true }, "cssstyle": { "version": "0.2.37", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "dev": true, "optional": true, "requires": { "cssom": "0.3.2" @@ -1451,6 +1482,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -1491,7 +1523,8 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "define-properties": { "version": "1.1.2", @@ -1535,7 +1568,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "depd": { "version": "1.1.0", @@ -1553,6 +1587,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, "requires": { "repeating": "2.0.1" } @@ -1571,6 +1606,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, "requires": { "domelementtype": "1.1.3", "entities": "1.1.1" @@ -1579,19 +1615,22 @@ "domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true } } }, "domelementtype": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true }, "domhandler": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "dev": true, "requires": { "domelementtype": "1.3.0" } @@ -1600,6 +1639,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, "requires": { "dom-serializer": "0.1.0", "domelementtype": "1.3.0" @@ -1615,6 +1655,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -1635,7 +1676,8 @@ "entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true }, "error-ex": { "version": "1.3.1", @@ -1751,17 +1793,20 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "dev": true, "optional": true, "requires": { "esprima": "3.1.3", @@ -1775,6 +1820,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "optional": true } } @@ -1795,6 +1841,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/esdoc/-/esdoc-1.0.4.tgz", "integrity": "sha512-Hy5sg0Lec4EDHVem3gFqNi+o6ZptivmaiHYacZhmn3hzLnHSMg2C1L0XTsDIcb4Cxd9aUnWdLAu6a6ghH/LLug==", + "dev": true, "requires": { "babel-generator": "6.26.0", "babel-traverse": "6.26.0", @@ -1813,6 +1860,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, "requires": { "chalk": "1.1.3", "esutils": "2.0.2", @@ -1823,6 +1871,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", + "dev": true, "requires": { "babel-messages": "6.23.0", "babel-runtime": "6.26.0", @@ -1838,6 +1887,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "2.4.1", "regenerator-runtime": "0.11.1" @@ -1847,6 +1897,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, "requires": { "babel-code-frame": "6.26.0", "babel-messages": "6.23.0", @@ -1863,6 +1914,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, "requires": { "babel-runtime": "6.26.0", "esutils": "2.0.2", @@ -1873,29 +1925,34 @@ "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, "esdoc-accessor-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-accessor-plugin/-/esdoc-accessor-plugin-1.0.0.tgz", - "integrity": "sha1-eRukhy5sQDUVznSbE0jW8Ck62es=" + "integrity": "sha1-eRukhy5sQDUVznSbE0jW8Ck62es=", + "dev": true }, "esdoc-brand-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-brand-plugin/-/esdoc-brand-plugin-1.0.0.tgz", "integrity": "sha1-niFtc15i/OxJ96M5u0Eh2mfMYDM=", + "dev": true, "requires": { "cheerio": "0.22.0" } @@ -1903,12 +1960,14 @@ "esdoc-coverage-plugin": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/esdoc-coverage-plugin/-/esdoc-coverage-plugin-1.1.0.tgz", - "integrity": "sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw=" + "integrity": "sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw=", + "dev": true }, "esdoc-external-ecmascript-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-external-ecmascript-plugin/-/esdoc-external-ecmascript-plugin-1.0.0.tgz", "integrity": "sha1-ePVl1KDFGFrGMVJhTc4f4ahmiNs=", + "dev": true, "requires": { "fs-extra": "1.0.0" } @@ -1916,22 +1975,26 @@ "esdoc-integrate-manual-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-integrate-manual-plugin/-/esdoc-integrate-manual-plugin-1.0.0.tgz", - "integrity": "sha1-GFSmqhwIEDXXyMUeO91PtlqkcRw=" + "integrity": "sha1-GFSmqhwIEDXXyMUeO91PtlqkcRw=", + "dev": true }, "esdoc-integrate-test-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-integrate-test-plugin/-/esdoc-integrate-test-plugin-1.0.0.tgz", - "integrity": "sha1-4tDQAJD38MNeXS8sAzMnp55T5Ak=" + "integrity": "sha1-4tDQAJD38MNeXS8sAzMnp55T5Ak=", + "dev": true }, "esdoc-lint-plugin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esdoc-lint-plugin/-/esdoc-lint-plugin-1.0.1.tgz", - "integrity": "sha1-h77mQD5nbAh/Yb6SxFLWDyxqcOU=" + "integrity": "sha1-h77mQD5nbAh/Yb6SxFLWDyxqcOU=", + "dev": true }, "esdoc-publish-html-plugin": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.0.tgz", "integrity": "sha1-CT+DN6yhaQIlcss4f/zD9HCwJRM=", + "dev": true, "requires": { "babel-generator": "6.11.4", "cheerio": "0.22.0", @@ -1946,6 +2009,7 @@ "version": "6.11.4", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz", "integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=", + "dev": true, "requires": { "babel-messages": "6.23.0", "babel-runtime": "6.23.0", @@ -1959,6 +2023,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz", "integrity": "sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U=", + "dev": true, "requires": { "get-stdin": "4.0.1", "minimist": "1.2.0", @@ -1968,17 +2033,20 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "dev": true, "requires": { "is-finite": "1.0.2" } @@ -1989,6 +2057,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-standard-plugin/-/esdoc-standard-plugin-1.0.0.tgz", "integrity": "sha1-ZhIBysfvhokkkCRG/awVJyU8XU0=", + "dev": true, "requires": { "esdoc-accessor-plugin": "1.0.0", "esdoc-brand-plugin": "1.0.0", @@ -2006,17 +2075,20 @@ "esdoc-type-inference-plugin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esdoc-type-inference-plugin/-/esdoc-type-inference-plugin-1.0.1.tgz", - "integrity": "sha1-qrynhkH5m9Hs5vMC8EW71jG+cvU=" + "integrity": "sha1-qrynhkH5m9Hs5vMC8EW71jG+cvU=", + "dev": true }, "esdoc-undocumented-identifier-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-undocumented-identifier-plugin/-/esdoc-undocumented-identifier-plugin-1.0.0.tgz", - "integrity": "sha1-guBdNxwy0ShxFA8dXIHsmf2cwsg=" + "integrity": "sha1-guBdNxwy0ShxFA8dXIHsmf2cwsg=", + "dev": true }, "esdoc-unexported-identifier-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-unexported-identifier-plugin/-/esdoc-unexported-identifier-plugin-1.0.0.tgz", - "integrity": "sha1-H5h0xqfCvr+a05fDzrdcnGnaurE=" + "integrity": "sha1-H5h0xqfCvr+a05fDzrdcnGnaurE=", + "dev": true }, "eslint": { "version": "3.19.0", @@ -2206,7 +2278,8 @@ "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true }, "esquery": { "version": "1.0.0", @@ -2230,7 +2303,8 @@ "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true }, "estree-walker": { "version": "0.2.1", @@ -2241,7 +2315,8 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true }, "event-emitter": { "version": "0.3.5", @@ -2295,7 +2370,8 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true }, "extend-shallow": { "version": "2.0.1", @@ -2318,7 +2394,8 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "ez-async": { "version": "1.0.0-alpha.1", @@ -2330,6 +2407,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true, "optional": true }, "fast-diff": { @@ -2342,12 +2420,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true, "optional": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "faye-websocket": { "version": "0.11.1", @@ -2480,12 +2560,14 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, "optional": true }, "form-data": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, "optional": true, "requires": { "asynckit": "0.4.0", @@ -2509,6 +2591,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, "requires": { "graceful-fs": "4.1.11", "jsonfile": "2.4.0", @@ -2527,6 +2610,910 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.9.2", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", @@ -2558,6 +3545,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -2621,7 +3609,8 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true }, "globby": { "version": "5.0.0", @@ -2640,7 +3629,8 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true }, "graceful-readlink": { "version": "1.0.1", @@ -2652,12 +3642,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, "optional": true }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, "optional": true, "requires": { "ajv": "5.5.2", @@ -2668,6 +3660,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, "optional": true, "requires": { "co": "4.6.0", @@ -2691,6 +3684,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -2714,6 +3708,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, "optional": true, "requires": { "boom": "4.3.1", @@ -2725,7 +3720,8 @@ "hoek": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true }, "home-or-tmp": { "version": "2.0.0", @@ -2756,6 +3752,7 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, "requires": { "domelementtype": "1.3.0", "domhandler": "2.4.1", @@ -2793,6 +3790,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", @@ -2804,6 +3802,7 @@ "version": "0.0.4", "resolved": "https://registry.npmjs.org/ice-cap/-/ice-cap-0.0.4.tgz", "integrity": "sha1-im0xq0ysjUtW3k+pRt8zUlYbbhg=", + "dev": true, "requires": { "cheerio": "0.20.0", "color-logger": "0.0.3" @@ -2813,6 +3812,7 @@ "version": "0.20.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.20.0.tgz", "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=", + "dev": true, "requires": { "css-select": "1.2.0", "dom-serializer": "0.1.0", @@ -2826,6 +3826,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, "requires": { "domelementtype": "1.3.0" } @@ -2834,6 +3835,7 @@ "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, "requires": { "domelementtype": "1.3.0", "domhandler": "2.3.0", @@ -2845,19 +3847,22 @@ "entities": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true } } }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -2868,7 +3873,8 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true } } }, @@ -2906,7 +3912,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ini": { "version": "1.3.4", @@ -2945,6 +3952,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true, "requires": { "loose-envify": "1.3.1" } @@ -3022,6 +4030,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -3141,6 +4150,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, "optional": true }, "is-utf8": { @@ -3170,7 +4180,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -3191,12 +4202,14 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, "optional": true }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true }, "js-yaml": { "version": "3.8.4", @@ -3212,12 +4225,14 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, "optional": true }, "jsdom": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", "integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=", + "dev": true, "optional": true, "requires": { "abab": "1.0.4", @@ -3241,6 +4256,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, "optional": true } } @@ -3248,18 +4264,21 @@ "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, "optional": true }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true, "optional": true }, "json-stable-stringify": { @@ -3275,6 +4294,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, "optional": true }, "json5": { @@ -3287,6 +4307,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, "requires": { "graceful-fs": "4.1.11" } @@ -3307,6 +4328,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", @@ -3334,6 +4356,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, "requires": { "graceful-fs": "4.1.11" } @@ -3351,6 +4374,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "1.1.2", "type-check": "0.3.2" @@ -3530,17 +4554,20 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true }, "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true }, "lodash.bind": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", + "dev": true }, "lodash.cond": { "version": "4.5.2", @@ -3551,52 +4578,62 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true }, "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true }, "lodash.merge": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", + "dev": true }, "lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true }, "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true }, "lodash.reject": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", + "dev": true }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true }, "longest": { "version": "1.0.1", @@ -3608,6 +4645,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, "requires": { "js-tokens": "3.0.2" } @@ -3646,7 +4684,8 @@ "marked": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", - "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=" + "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=", + "dev": true }, "matched": { "version": "0.4.4", @@ -3715,12 +4754,14 @@ "mime-db": { "version": "1.27.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true }, "mime-types": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true, "requires": { "mime-db": "1.27.0" } @@ -3773,6 +4814,13 @@ "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", "dev": true }, + "nan": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", + "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", + "dev": true, + "optional": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3810,6 +4858,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, "requires": { "boolbase": "1.0.0" } @@ -3817,18 +4866,21 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nwmatcher": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", "integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==", + "dev": true, "optional": true }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, "optional": true }, "object-assign": { @@ -3898,6 +4950,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, "requires": { "deep-is": "0.1.3", "fast-levenshtein": "2.0.6", @@ -3910,7 +4963,8 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true } } }, @@ -3989,6 +5043,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", + "dev": true, "optional": true }, "parseurl": { @@ -4048,6 +5103,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true, "optional": true }, "pify": { @@ -4130,7 +5186,8 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "preserve": { "version": "0.2.0", @@ -4147,7 +5204,8 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true }, "progress": { "version": "1.1.8", @@ -4158,12 +5216,14 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true, "optional": true }, "quill": { @@ -4303,6 +5363,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -4364,7 +5425,8 @@ "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true }, "regenerator-transform": { "version": "0.9.11", @@ -4443,6 +5505,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, "requires": { "is-finite": "1.0.2" } @@ -4451,6 +5514,7 @@ "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "dev": true, "optional": true, "requires": { "aws-sign2": "0.7.0", @@ -4481,12 +5545,14 @@ "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, "optional": true }, "mime-types": { "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, "optional": true, "requires": { "mime-db": "1.33.0" @@ -4717,12 +5783,14 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, "optional": true }, "semver": { @@ -4800,6 +5868,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, "optional": true, "requires": { "hoek": "4.2.1" @@ -4808,7 +5877,8 @@ "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true }, "source-map-support": { "version": "0.4.15", @@ -4865,6 +5935,7 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, "optional": true, "requires": { "asn1": "0.2.3", @@ -4980,6 +6051,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -4988,12 +6060,14 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, "optional": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -5030,12 +6104,14 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true }, "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true, "optional": true }, "table": { @@ -5088,7 +6164,8 @@ "taffydb": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.7.2.tgz", - "integrity": "sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg=" + "integrity": "sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg=", + "dev": true }, "tag-dist-files": { "version": "0.1.6", @@ -5123,7 +6200,8 @@ "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true }, "to-object-path": { "version": "0.3.0", @@ -5138,6 +6216,7 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, "requires": { "punycode": "1.4.1" } @@ -5146,6 +6225,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true, "optional": true }, "tree-kill": { @@ -5163,7 +6243,8 @@ "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true }, "tryit": { "version": "1.0.3", @@ -5175,6 +6256,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "optional": true, "requires": { "safe-buffer": "5.1.1" @@ -5184,12 +6266,14 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, "optional": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "1.1.2" } @@ -5245,7 +6329,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "utils-merge": { "version": "1.0.0", @@ -5256,7 +6341,8 @@ "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true }, "v8flags": { "version": "2.1.1", @@ -5287,6 +6373,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "optional": true, "requires": { "assert-plus": "1.0.0", @@ -5304,6 +6391,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", "integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=", + "dev": true, "optional": true }, "websocket-driver": { @@ -5325,6 +6413,7 @@ "version": "0.6.5", "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz", "integrity": "sha1-AImBEa9om7CXVBzVpFymyHmERb8=", + "dev": true, "optional": true, "requires": { "tr46": "0.0.3" @@ -5370,6 +6459,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", + "dev": true, "optional": true }, "xtend": { diff --git a/package.json b/package.json index 4a48e265..484e811b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "test": "npm run lint", "debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'", "lint": "standard", - "docs": "esdocs", + "docs": "esdoc", "serve-docs": "npm run docs && serve ./docs/", "dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js", "watch": "concurrently 'rollup -wc rollup.browser.js' 'rollup -wc rollup.node.js'", @@ -56,6 +56,8 @@ "chance": "^1.0.9", "concurrently": "^3.4.0", "cutest": "^0.1.9", + "esdoc": "^1.0.4", + "esdoc-standard-plugin": "^1.0.0", "quill": "^1.3.5", "quill-cursors": "^1.0.2", "rollup-plugin-babel": "^2.7.1", @@ -67,9 +69,7 @@ "rollup-regenerator-runtime": "^6.23.1", "rollup-watch": "^3.2.2", "standard": "^10.0.2", - "tag-dist-files": "^0.1.6", - "esdoc": "^1.0.4", - "esdoc-standard-plugin": "^1.0.0" + "tag-dist-files": "^0.1.6" }, "dependencies": { "debug": "^2.6.8" diff --git a/rollup.browser.js b/rollup.browser.js index d5f6c137..a0b87065 100644 --- a/rollup.browser.js +++ b/rollup.browser.js @@ -19,7 +19,8 @@ export default { browser: true }), commonjs(), - babel(), + // babel(), + /* uglify({ mangle: { except: ['YMap', 'Y', 'YArray', 'YText', 'YXmlHook', 'YXmlFragment', 'YXmlElement', 'YXmlEvent', 'YXmlText', 'YEvent', 'YArrayEvent', 'YMapEvent', 'Type', 'Delete', 'ItemJSON', 'ItemString', 'Item'] @@ -35,6 +36,7 @@ export default { } } }) + */ ], banner: ` /** diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js index 06b46c1c..d8b06dab 100644 --- a/src/Bindings/DomBinding/DomBinding.js +++ b/src/Bindings/DomBinding/DomBinding.js @@ -1,109 +1,15 @@ /* global MutationObserver */ -import Binding from './Binding.js' -import diff from '../Util/simpleDiff.js' -import YXmlFragment from '../../Type/YXml/YXmlFragment.js' -import YXmlHook from '../../Type/YXml/YXmlHook.js' - - -function defaultFilter (nodeName, attrs) { - return attrs -} - -function applyFilter (target, filter, type) { - if (type._deleted) { - return - } - // check if type is a child of this - let isChild = false - let p = type - while (p !== undefined) { - if (p === target) { - isChild = true - break - } - p = p._parent - } - if (!isChild) { - return - } - // filter attributes - const attributes = new Map() - if (type.getAttributes !== undefined) { - let attrs = type.getAttributes() - for (let key in attrs) { - attributes.set(key, attrs[key]) - } - } - let result = filter(type.nodeName, new Map(attributes)) - if (result === null) { - type._delete(this._y) - } else { - attributes.forEach((value, key) => { - if (!result.has(key)) { - type.removeAttribute(key) - } - }) - } -} - -function typeObserver (events) { - this._mutualExclude(() => { - reflectChangesOnDom.call(this, events) - }) -} - -function domObserver (mutations) { - this._mutualExclude(() => { - this._y.transact(() => { - let diffChildren = new Set() - mutations.forEach(mutation => { - const dom = mutation.target - const yxml = this.domToYXml.get(dom._yxml) - if (yxml == null || yxml.constructor === YXmlHook) { - // dom element is filtered - return - } - switch (mutation.type) { - case 'characterData': - var change = diff(yxml.toString(), dom.nodeValue) - yxml.delete(change.pos, change.remove) - yxml.insert(change.pos, change.insert) - break - case 'attributes': - if (yxml.constructor === YXmlFragment) { - break - } - let name = mutation.attributeName - let val = dom.getAttribute(name) - // check if filter accepts attribute - let attributes = new Map() - attributes.set(name, val) - if (this.filter(dom.nodeName, attributes).size > 0 && yxml.constructor !== YXmlFragment) { - if (yxml.getAttribute(name) !== val) { - if (val == null) { - yxml.removeAttribute(name) - } else { - yxml.setAttribute(name, val) - } - } - } - break - case 'childList': - diffChildren.add(mutation.target) - break - } - }) - for (let dom of diffChildren) { - if (dom.yOnChildrenChanged !== undefined) { - dom.yOnChildrenChanged() - } - const yxml = this.domToType.get(dom) - applyChangesFromDom(dom, yxml) - } - }) - }) -} +import Binding from '../Binding.js' +import diff from '../../Util/simpleDiff.js' +import YXmlFragment from '../../Types/YXml/YXmlFragment.js' +import YXmlHook from '../../Types/YXml/YXmlHook.js' +import { removeDomChildrenUntilElementFound, createAssociation } from './util.js' +import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js' +import { defaultFilter, applyFilterOnType } from './filter.js' +import typeObserver from './typeObserver.js' +import domObserver from './domObserver.js' +import { removeAssociation } from './util.js' /** * A binding that binds the children of a YXmlFragment to a DOM element. @@ -122,7 +28,7 @@ export default class DomBinding extends Binding { * truth. * @param {Element} target The bind target. Mirrors the target. */ - constructor (type, target, opts) { + constructor (type, target, opts = {}) { // Binding handles textType as this.type and domTextarea as this.target super(type, target) this.domToType = new Map() @@ -134,20 +40,73 @@ export default class DomBinding extends Binding { target.insertBefore(child.toDom(this.domToType, this.typeToDom), null) } this._typeObserver = typeObserver.bind(this) - this._domObserver = domObserver.bind(this) - type.observe(this._typeObserver) - this._domObserver = domObserver.bind(this) - this._mutationObserver = new MutationObserver(this._domObserver()) + this._domObserver = (mutations) => { + domObserver.call(this, mutations, opts._document) + } + type.observeDeep(this._typeObserver) + this._mutationObserver = new MutationObserver(this._domObserver) this._mutationObserver.observe(target, { childList: true, attributes: true, characterData: true, subtree: true }) - this._beforeTransactionHandler = () => { - this._domObserverListener(this._domObserver.takeRecords()) + const y = type._y + // Force flush dom changes before Type changes are applied (they might + // modify the dom) + this._beforeTransactionHandler = (y, transaction, remote) => { + this._domObserver(this._mutationObserver.takeRecords()) + beforeTransactionSelectionFixer(y, this, transaction, remote) } - this._y.on('beforeTransaction', this._beforeTransactionHandler) + y.on('beforeTransaction', this._beforeTransactionHandler) + this._afterTransactionHandler = (y, transaction, remote) => { + afterTransactionSelectionFixer(y, this, transaction, remote) + // remove associations + // TODO: this could be done more efficiently + // e.g. Always delete using the following approach, or removeAssociation + // in dom/type-observer.. + transaction.deletedStructs.forEach(type => { + const dom = this.typeToDom.get(type) + if (dom !== undefined) { + removeAssociation(this, dom, type) + } + }) + } + y.on('afterTransaction', this._afterTransactionHandler) + // Before calling observers, apply dom filter to all changed and new types. + this._beforeObserverCallsHandler = (y, transaction) => { + // Apply dom filter to new and changed types + transaction.changedTypes.forEach((subs, type) => { + // Only check attributes. New types are filtered below. + if ((subs.size > 1 || (subs.size === 1 && subs.has(null) === false))) { + applyFilterOnType(y, this, type) + } + }) + transaction.newTypes.forEach(type => { + applyFilterOnType(y, this, type) + }) + } + y.on('beforeObserverCalls', this._beforeObserverCallsHandler) + createAssociation(this, target, type) + } + + /** + * Enables the smart scrolling functionality for a Dom Binding. + * This is useful when YXml is bound to a shared editor. When activated, + * the viewport will be changed to accommodate remote changes. + * + * @param {Element} scrollElement The node that is + */ + enableSmartScrolling (scrollElement) { + // @TODO: implement smart scrolling + } + + /** + * NOTE: currently does not apply filter to existing elements! + */ + setFilter (filter) { + this.filter = filter + // TODO: apply filter to all elements } /** @@ -158,7 +117,11 @@ export default class DomBinding extends Binding { this.typeToDom = null this.type.unobserve(this._typeObserver) this._mutationObserver.disconnect() - this.type._y.off('beforeTransaction', this._beforeTransactionHandler) + const y = this.type._y + y.off('beforeTransaction', this._beforeTransactionHandler) + y.off('beforeObserverCalls', this._beforeObserverCallsHandler) + y.off('afterObserverCalls', this._afterObserverCallsHandler) + y.off('afterTransaction', this._afterTransactionHandler) super.destroy() } } diff --git a/src/Bindings/DomBinding/applyChangesFromDom.js b/src/Bindings/DomBinding/applyChangesFromDom.js deleted file mode 100644 index 94578ef7..00000000 --- a/src/Bindings/DomBinding/applyChangesFromDom.js +++ /dev/null @@ -1,75 +0,0 @@ -import YXmlHook from '../../YXml/YXmlHook.js' -import { - iterateUntilUndeleted, - removeAssociation, - insertNodeHelper } from './util.js' - -/* - * 1. Check if any of the nodes was deleted - * 2. Iterate over the children. - * 2.1 If a node exists without _yxml property, insert a new node - * 2.2 If _contents.length < dom.childNodes.length, fill the - * rest of _content with childNodes - * 2.3 If a node was moved, delete it and - * recreate a new yxml element that is bound to that node. - * You can detect that a node was moved because expectedId - * !== actualId in the list - */ -export default function applyChangesFromDom (dom, yxml) { - if (yxml == null || yxml === false || yxml.constructor === YXmlHook) { - return - } - const y = yxml._y - const knownChildren = new Set() - for (let child in dom.childNodes) { - const type = knownChildren.get(child) - if (type !== undefined && type !== false) { - knownChildren.add(type) - } - } - // 1. Check if any of the nodes was deleted - yxml.forEach(function (childType) { - if (knownChildren.has(childType) === false) { - childType._delete(y) - } - }) - // 2. iterate - const childNodes = dom.childNodes - const len = childNodes.length - let prevExpectedType = null - let expectedType = iterateUntilUndeleted(yxml._start) - for (let domCnt = 0; domCnt < len; domCnt++) { - const childNode = childNodes[domCnt] - const childType = this.domToYXml.get(childNode) - if (childType != null) { - if (childType === false) { - // should be ignored or is going to be deleted - continue - } - if (expectedType !== null) { - if (expectedType !== childType) { - // 2.3 Not expected node - if (childType._parent !== yxml) { - // child was moved from another parent - // childType is going to be deleted by its previous parent - removeAssociation(this, childNode, this.domToYXml(childNode)) - } else { - // child was moved to a different position. - childType._delete(y) - } - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode) - } else { - // Found expected node - prevExpectedType = expectedType - expectedType = iterateUntilUndeleted(expectedType._right) - } - } else { - // 2.2 Fill _content with child nodes - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode) - } - } else { - // 2.1 A new node was found - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode) - } - } -} diff --git a/src/Bindings/DomBinding/domObserver.js b/src/Bindings/DomBinding/domObserver.js new file mode 100644 index 00000000..f0f4d6bb --- /dev/null +++ b/src/Bindings/DomBinding/domObserver.js @@ -0,0 +1,132 @@ + +import YXmlHook from '../../Types/YXml/YXmlHook.js' +import { + iterateUntilUndeleted, + removeAssociation, + insertNodeHelper } from './util.js' +import diff from '../../Util/simpleDiff.js' +import YXmlFragment from '../../Types/YXml/YXmlFragment.js' + +/* + * 1. Check if any of the nodes was deleted + * 2. Iterate over the children. + * 2.1 If a node exists that is not yet bound to a type, insert a new node + * 2.2 If _contents.length < dom.childNodes.length, fill the + * rest of _content with childNodes + * 2.3 If a node was moved, delete it and + * recreate a new yxml element that is bound to that node. + * You can detect that a node was moved because expectedId + * !== actualId in the list + */ +function applyChangesFromDom (binding, dom, yxml, _document) { + if (yxml == null || yxml === false || yxml.constructor === YXmlHook) { + return + } + const y = yxml._y + const knownChildren = new Set() + for (let i = dom.childNodes.length - 1; i >= 0; i--) { + const type = binding.domToType.get(dom.childNodes[i]) + if (type !== undefined && type !== false) { + knownChildren.add(type) + } + } + // 1. Check if any of the nodes was deleted + yxml.forEach(function (childType) { + if (knownChildren.has(childType) === false) { + childType._delete(y) + removeAssociation(binding, binding.typeToDom.get(childType), childType) + } + }) + // 2. iterate + const childNodes = dom.childNodes + const len = childNodes.length + let prevExpectedType = null + let expectedType = iterateUntilUndeleted(yxml._start) + for (let domCnt = 0; domCnt < len; domCnt++) { + const childNode = childNodes[domCnt] + const childType = binding.domToType.get(childNode) + if (childType !== undefined) { + if (childType === false) { + // should be ignored or is going to be deleted + continue + } + if (expectedType !== null) { + if (expectedType !== childType) { + // 2.3 Not expected node + if (childType._parent !== yxml) { + // child was moved from another parent + // childType is going to be deleted by its previous parent + removeAssociation(binding, childNode, childType) + } else { + // child was moved to a different position. + childType._delete(y) + removeAssociation(binding, childNode, childType) + } + prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) + } else { + // Found expected node. Continue. + prevExpectedType = expectedType + expectedType = iterateUntilUndeleted(expectedType._right) + } + } else { + // 2.2 Fill _content with child nodes + prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) + } + } else { + // 2.1 A new node was found + prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) + } + } +} + +export default function domObserver (mutations, _document) { + this._mutualExclude(() => { + this.type._y.transact(() => { + let diffChildren = new Set() + mutations.forEach(mutation => { + const dom = mutation.target + const yxml = this.domToType.get(dom) + if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) { + // dom element is filtered + return + } + switch (mutation.type) { + case 'characterData': + var change = diff(yxml.toString(), dom.nodeValue) + yxml.delete(change.pos, change.remove) + yxml.insert(change.pos, change.insert) + break + case 'attributes': + if (yxml.constructor === YXmlFragment) { + break + } + let name = mutation.attributeName + let val = dom.getAttribute(name) + // check if filter accepts attribute + let attributes = new Map() + attributes.set(name, val) + if (yxml.constructor !== YXmlFragment && this.filter(dom.nodeName, attributes).size > 0) { + if (yxml.getAttribute(name) !== val) { + if (val == null) { + yxml.removeAttribute(name) + } else { + yxml.setAttribute(name, val) + } + } + } + break + case 'childList': + diffChildren.add(mutation.target) + break + } + }) + for (let dom of diffChildren) { + if (dom.yOnChildrenChanged !== undefined) { + dom.yOnChildrenChanged() + } + const yxml = this.domToType.get(dom) + applyChangesFromDom(this, dom, yxml, _document) + } + }) + }) +} diff --git a/src/Bindings/DomBinding/domToType.js b/src/Bindings/DomBinding/domToType.js new file mode 100644 index 00000000..ddf21a6d --- /dev/null +++ b/src/Bindings/DomBinding/domToType.js @@ -0,0 +1,32 @@ + +import { YXmlText, YXmlElement } from '../../Types/YXml/YXml.js' +import { createAssociation } from './util.js' + +/** + * Creates a Yjs type (YXml) based on the contents of a DOM Element. + * + * @param {Element|TextNode} + */ +export default function domToType (element, _document = document, binding) { + let type + switch (element.nodeType) { + case _document.ELEMENT_NODE: + type = new YXmlElement(element.nodeName) + const attrs = element.attributes + for (let i = attrs.length - 1; i >= 0; i--) { + const attr = attrs[i] + type.setAttribute(attr.name, attr.value) + } + const children = Array.from(element.childNodes).map(e => domToType(e, _document, binding)) + type.insert(0, children) + break + case _document.TEXT_NODE: + type = new YXmlText() + type.insert(0, element.nodeValue) + break + default: + throw new Error('Can\'t transform this node type to a YXml type!') + } + createAssociation(binding, element, type) + return type +} diff --git a/src/Bindings/DomBinding/filter.js b/src/Bindings/DomBinding/filter.js new file mode 100644 index 00000000..e9cb8b87 --- /dev/null +++ b/src/Bindings/DomBinding/filter.js @@ -0,0 +1,31 @@ +import isParentOf from '../../Util/isParentOf.js' + +export function defaultFilter (nodeName, attrs) { + return attrs +} + + +export function applyFilterOnType (y, binding, type) { + if (isParentOf(binding.type, type)) { + const nodeName = type.nodeName + let attributes = new Map() + if (type.getAttributes !== undefined) { + let attrs = type.getAttributes() + for (let key in attrs) { + attributes.set(key, attrs[key]) + } + } + const filteredAttributes = binding.filter(nodeName, new Map(attributes)) + if (filteredAttributes === null) { + type._delete(y) + } else { + // iterate original attributes + attributes.forEach((value, key) => { + // delete all attributes that are not in filteredAttributes + if (filteredAttributes.has(key) === false) { + type.removeAttribute(key) + } + }) + } + } +} diff --git a/src/Types/YXml/selection.js b/src/Bindings/DomBinding/selection.js similarity index 70% rename from src/Types/YXml/selection.js rename to src/Bindings/DomBinding/selection.js index 428217f0..551f4e87 100644 --- a/src/Types/YXml/selection.js +++ b/src/Bindings/DomBinding/selection.js @@ -7,30 +7,30 @@ let relativeSelection = null export let beforeTransactionSelectionFixer if (typeof getSelection !== 'undefined') { - beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, transaction, remote) { + beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, domBinding, transaction, remote) { if (!remote) { return } relativeSelection = { from: null, to: null, fromY: null, toY: null } browserSelection = getSelection() const anchorNode = browserSelection.anchorNode - if (anchorNode !== null && anchorNode._yxml != null) { - const yxml = anchorNode._yxml - relativeSelection.from = getRelativePosition(yxml, browserSelection.anchorOffset) - relativeSelection.fromY = yxml._y + const anchorNodeType = domBinding.domToType.get(anchorNode) + if (anchorNode !== null && anchorNodeType !== undefined) { + relativeSelection.from = getRelativePosition(anchorNodeType, browserSelection.anchorOffset) + relativeSelection.fromY = anchorNodeType._y } const focusNode = browserSelection.focusNode - if (focusNode !== null && focusNode._yxml != null) { - const yxml = focusNode._yxml - relativeSelection.to = getRelativePosition(yxml, browserSelection.focusOffset) - relativeSelection.toY = yxml._y + const focusNodeType = domBinding.domToType.get(focusNode) + if (focusNode !== null && focusNodeType !== undefined) { + relativeSelection.to = getRelativePosition(focusNodeType, browserSelection.focusOffset) + relativeSelection.toY = focusNodeType._y } } } else { beforeTransactionSelectionFixer = function _fakeBeforeTransactionSelectionFixer () {} } -export function afterTransactionSelectionFixer (y, transaction, remote) { +export function afterTransactionSelectionFixer (y, domBinding, transaction, remote) { if (relativeSelection === null || !remote) { return } @@ -46,7 +46,7 @@ export function afterTransactionSelectionFixer (y, transaction, remote) { if (from !== null) { let sel = fromRelativePosition(fromY, from) if (sel !== null) { - let node = sel.type.getDom() + let node = domBinding.typeToDom.get(sel.type) let offset = sel.offset if (node !== anchorNode || offset !== anchorOffset) { anchorNode = node @@ -58,7 +58,7 @@ export function afterTransactionSelectionFixer (y, transaction, remote) { if (to !== null) { let sel = fromRelativePosition(toY, to) if (sel !== null) { - let node = sel.type.getDom() + let node = domBinding.typeToDom.get(sel.type) let offset = sel.offset if (node !== focusNode || offset !== focusOffset) { focusNode = node diff --git a/src/Bindings/DomBinding/typeObserver.js b/src/Bindings/DomBinding/typeObserver.js new file mode 100644 index 00000000..46f8c79d --- /dev/null +++ b/src/Bindings/DomBinding/typeObserver.js @@ -0,0 +1,61 @@ + +import YXmlText from '../../Types/YXml/YXmlText.js' +import YXmlHook from '../../Types/YXml/YXmlHook.js' +import { removeDomChildrenUntilElementFound } from './util.js' + +export default function typeObserver (events, _document) { + this._mutualExclude(() => { + events.forEach(event => { + const yxml = event.target + const dom = this.typeToDom.get(yxml) + if (dom !== undefined && dom !== false) { + if (yxml.constructor === YXmlText) { + dom.nodeValue = yxml.toString() + // TODO: use hasOwnProperty instead of === undefined check + } else if (event.attributesChanged !== undefined) { + // update attributes + event.attributesChanged.forEach(attributeName => { + const value = yxml.getAttribute(attributeName) + if (value === undefined) { + dom.removeAttribute(attributeName) + } else { + dom.setAttribute(attributeName, value) + } + }) + /* + * TODO: instead of hard-checking the types, it would be best to + * specify the type's features. E.g. + * - _yxmlHasAttributes + * - _yxmlHasChildren + * Furthermore, the features shouldn't be encoded in the types, + * only in the attributes (above) + */ + if (event.childListChanged && yxml.constructor !== YXmlHook) { + let currentChild = dom.firstChild + yxml.forEach(childType => { + const childNode = this.typeToDom.get(childType) + const binding = this + switch (childNode) { + case undefined: + // Does not exist. Create it. + const node = childType.toDom(_document, binding) + dom.insertBefore(node, currentChild) + break + case false: + // nop + break + default: + // Is already attached to the dom. + // Find it and remove all dom nodes in-between. + removeDomChildrenUntilElementFound(dom, currentChild, childNode) + currentChild = childNode.nextSibling + break + } + }) + removeDomChildrenUntilElementFound(dom, currentChild, null) + } + } + } + }) + }) +} diff --git a/src/Bindings/DomBinding/util.js b/src/Bindings/DomBinding/util.js index cc2e3e64..f8e5c94c 100644 --- a/src/Bindings/DomBinding/util.js +++ b/src/Bindings/DomBinding/util.js @@ -1,4 +1,6 @@ +import domToType from './domToType.js' + export function iterateUntilUndeleted (item) { while (item !== null && item._deleted) { item = item._right @@ -12,15 +14,51 @@ export function removeAssociation (domBinding, dom, type) { } export function createAssociation (domBinding, dom, type) { - domBinding.domToType.set(dom, type) - domBinding.typeToDom.set(type, dom) + if (domBinding !== undefined) { + domBinding.domToType.set(dom, type) + domBinding.typeToDom.set(type, dom) + } } -function insertNodeHelper (yxml, prevExpectedNode, child) { - let insertedNodes = yxml.insertDomElementsAfter(prevExpectedNode, [child]) +/** + * Insert Dom Elements after one of the children of this YXmlFragment. + * The Dom elements will be bound to a new YXmlElement and inserted at the + * specified position. + * + * @param {YXmlElement} type The type in which to insert DOM elements. + * @param {YXmlElement|null} prev The reference node. New YxmlElements are + * inserted after this node. Set null to insert at + * the beginning. + * @param {Array} doms The Dom elements to insert. + * @param {?Document} _document Optional. Provide the global document object. + * @return {Array} The YxmlElements that are inserted. + */ +export function insertDomElementsAfter (type, prev, doms, _document, binding) { + return type.insertAfter(prev, doms.map(dom => domToType(dom, _document, binding))) +} + +export function insertNodeHelper (yxml, prevExpectedNode, child, _document, binding) { + let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding) if (insertedNodes.length > 0) { return insertedNodes[0] } else { return prevExpectedNode } } + + +/** + * Remove children until `elem` is found. + * + * @param {Element} parent The parent of `elem` and `currentChild`. + * @param {Element} currentChild Start removing elements with `currentChild`. If + * `currentChild` is `elem` it won't be removed. + * @param {Element|null} elem The elemnt to look for. + */ +export function removeDomChildrenUntilElementFound (parent, currentChild, elem) { + while (currentChild !== elem) { + const del = currentChild + currentChild = currentChild.nextSibling + parent.removeChild(del) + } +} diff --git a/src/Bindings/QuillBinding/QuillBinding.js b/src/Bindings/QuillBinding/QuillBinding.js index e88a6c44..02995ec9 100644 --- a/src/Bindings/QuillBinding/QuillBinding.js +++ b/src/Bindings/QuillBinding/QuillBinding.js @@ -1,11 +1,14 @@ -import Binding from './Binding.js' +import Binding from '../Binding.js' function typeObserver (event) { const quill = this.target + // Force flush Quill changes. quill.update('yjs') this._mutualExclude(function () { + // Apply computed delta. quill.updateContents(event.delta, 'yjs') - quill.update('yjs') // ignore applied changes + // Force flush Quill changes. Ignore applied changes. + quill.update('yjs') }) } @@ -16,12 +19,14 @@ function quillObserver (delta) { } /** - * A Binding that binds a YText type to a Quill editor + * A Binding that binds a YText type to a Quill editor. * * @example - * const quill = new Quill(document.createElement('div')) - * const type = y.define('quill', Y.Text) - * const binding = new Y.QuillBinding(quill, type) + * const quill = new Quill(document.createElement('div')) + * const type = y.define('quill', Y.Text) + * const binding = new Y.QuillBinding(quill, type) + * // Now modifications on the DOM will be reflected in the Type, and the other + * // way around! */ export default class QuillBinding extends Binding { /** @@ -29,18 +34,18 @@ export default class QuillBinding extends Binding { * @param {Quill} quill */ constructor (textType, quill) { - // Binding handles textType as this.type and quill as this.target + // Binding handles textType as this.type and quill as this.target. super(textType, quill) - // set initial value + // Set initial value. quill.setContents(textType.toDelta(), 'yjs') - // Observers are handled by this class + // Observers are handled by this class. this._typeObserver = typeObserver.bind(this) this._quillObserver = quillObserver.bind(this) textType.observe(this._typeObserver) quill.on('text-change', this._quillObserver) } destroy () { - // Remove everything that is handled by this class + // Remove everything that is handled by this class. this.type.unobserve(this._typeObserver) this.target.off('text-change', this._quillObserver) super.destroy() diff --git a/src/Bindings/TextareaBinding/TextareaBinding.js b/src/Bindings/TextareaBinding/TextareaBinding.js index b09fb0a7..e02edafe 100644 --- a/src/Bindings/TextareaBinding/TextareaBinding.js +++ b/src/Bindings/TextareaBinding/TextareaBinding.js @@ -1,7 +1,7 @@ -import Binding from './Binding.js' -import simpleDiff from '../Util/simpleDiff.js' -import { getRelativePosition, fromRelativePosition } from '../Util/relativePosition.js' +import Binding from '../Binding.js' +import simpleDiff from '../../Util/simpleDiff.js' +import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.js' function typeObserver () { this._mutualExclude(() => { diff --git a/src/Types/YArray/YArray.js b/src/Types/YArray/YArray.js index 80033781..a901a89b 100644 --- a/src/Types/YArray/YArray.js +++ b/src/Types/YArray/YArray.js @@ -302,6 +302,7 @@ export default class YArray extends Type { } } }) + return content } /** diff --git a/src/Types/YXml/YXmlElement.js b/src/Types/YXml/YXmlElement.js index dfccdf1a..382396aa 100644 --- a/src/Types/YXml/YXmlElement.js +++ b/src/Types/YXml/YXmlElement.js @@ -1,7 +1,6 @@ -import { defaultDomFilter } from './utils.js' - import YMap from '../YMap/YMap.js' import { YXmlFragment } from './YXml.js' +import { createAssociation } from '../../Bindings/DomBinding/util.js' /** * An YXmlElement imitates the behavior of a @@ -10,25 +9,12 @@ import { YXmlFragment } from './YXml.js' * * An YXmlElement has attributes (key value pairs) * * An YXmlElement has childElements that must inherit from YXmlElement * - * @param {String} arg1 Node name - * @param {Function} arg2 Dom filter + * @param {String} nodeName Node name */ export default class YXmlElement extends YXmlFragment { - constructor (arg1, arg2, _document) { + constructor (nodeName = 'UNDEFINED') { super() - this.nodeName = null - this._scrollElement = null - if (typeof arg1 === 'string') { - this.nodeName = arg1.toUpperCase() - } else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) { - this.nodeName = arg1.nodeName - this._setDom(arg1, _document) - } else { - this.nodeName = 'UNDEFINED' - } - if (typeof arg2 === 'function') { - this._domFilter = arg2 - } + this.nodeName = nodeName.toUpperCase() } /** @@ -41,48 +27,6 @@ export default class YXmlElement extends YXmlFragment { return struct } - /** - * @private - * Copies children and attributes from a dom node to this YXmlElement. - */ - _setDom (dom, _document) { - if (this._dom != null) { - throw new Error('Only call this method if you know what you are doing ;)') - } else if (dom._yxml != null) { // TODO do i need to check this? - no.. but for dev purps.. - throw new Error('Already bound to an YXml type') - } else { - // tag is already set in constructor - // set attributes - let attributes = new Map() - for (let i = 0; i < dom.attributes.length; i++) { - let attr = dom.attributes[i] - // get attribute via getAttribute for custom element support (some write something different in attr.value) - attributes.set(attr.name, dom.getAttribute(attr.name)) - } - attributes = this._domFilter(dom, attributes) - attributes.forEach((value, name) => { - this.setAttribute(name, value) - }) - this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes), _document) - this._bindToDom(dom, _document) - return dom - } - } - - /** - * @private - * Bind a dom to to this YXmlElement. This means that the DOM changes when the - * YXmlElement is modified and that this YXmlElement changes when the DOM is - * modified. - * - * Currently only works in YXmlFragment. - */ - _bindToDom (dom, _document) { - _document = _document || document - this._dom = dom - dom._yxml = this - } - /** * @private * Read the next Item in a Decoder and fill this Item with the read data. @@ -127,9 +71,6 @@ export default class YXmlElement extends YXmlFragment { if (this.nodeName === null) { throw new Error('nodeName must be defined!') } - if (this._domFilter === defaultDomFilter && this._parent._domFilter !== undefined) { - this._domFilter = this._parent._domFilter - } super._integrate(y) } @@ -206,21 +147,16 @@ export default class YXmlElement extends YXmlFragment { * * @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} */ - getDom (_document) { - _document = _document || document - let dom = this._dom - if (dom == null) { - dom = _document.createElement(this.nodeName) - dom._yxml = this - let attrs = this.getAttributes() - for (let key in attrs) { - dom.setAttribute(key, attrs[key]) - } - this.forEach(yxml => { - dom.appendChild(yxml.getDom(_document)) - }) - this._bindToDom(dom, _document) + toDom (_document = document, binding) { + const dom = _document.createElement(this.nodeName) + let attrs = this.getAttributes() + for (let key in attrs) { + dom.setAttribute(key, attrs[key]) } + this.forEach(yxml => { + dom.appendChild(yxml.toDom(_document, binding)) + }) + createAssociation(binding, dom, this) return dom } } diff --git a/src/Types/YXml/YXmlEvent.js b/src/Types/YXml/YXmlEvent.js index b27eb139..1fd884f4 100644 --- a/src/Types/YXml/YXmlEvent.js +++ b/src/Types/YXml/YXmlEvent.js @@ -4,8 +4,9 @@ import YEvent from '../../Util/YEvent.js' * An Event that describes changes on a YXml Element or Yxml Fragment */ export default class YXmlEvent extends YEvent { - constructor (target, subs, remote) { + constructor (target, subs, remote, transaction) { super(target) + this._transaction = transaction this.childListChanged = false this.attributesChanged = new Set() this.remote = remote diff --git a/src/Types/YXml/YXmlFragment.js b/src/Types/YXml/YXmlFragment.js index 5f98042c..6661fdd7 100644 --- a/src/Types/YXml/YXmlFragment.js +++ b/src/Types/YXml/YXmlFragment.js @@ -1,7 +1,7 @@ /* global MutationObserver */ -import { defaultDomFilter, applyChangesFromDom, reflectChangesOnDom } from './utils.js' -import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer } from './selection.js' +import { createAssociation } from '../../Bindings/DomBinding/util.js' +import YXmlTreeWalker from './YXmlTreeWalker.js' import YArray from '../YArray/YArray.js' import YXmlEvent from './YXmlEvent.js' @@ -9,33 +9,6 @@ import { YXmlText, YXmlHook } from './YXml.js' import { logID } from '../../MessageHandler/messageToString.js' import diff from '../../Util/simpleDiff.js' -function domToYXml (parent, doms, _document) { - const types = [] - doms.forEach(d => { - if (d._yxml != null && d._yxml !== false) { - d._yxml._unbindFromDom() - } - if (parent._domFilter(d.nodeName, new Map()) !== null) { - let type - const hookName = d._yjsHook || (d.dataset != null ? d.dataset.yjsHook : undefined) - if (hookName !== undefined) { - type = new YXmlHook(hookName, d) - } else if (d.nodeType === d.TEXT_NODE) { - type = new YXmlText(d) - } else if (d.nodeType === d.ELEMENT_NODE) { - type = new YXmlFragment._YXmlElement(d, parent._domFilter, _document) - } else { - throw new Error('Unsupported node!') - } - // type.enableSmartScrolling(parent._scrollElement) - types.push(type) - } else { - d._yxml = false - } - }) - return types -} - /** * Define the elements to which a set of CSS queries apply. * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} @@ -48,64 +21,6 @@ function domToYXml (parent, doms, _document) { * @typedef {string} CSS_Selector */ -/** - * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a - * position within them. - * - * Can be created with {@link YXmlFragment#createTreeWalker} - */ -class YXmlTreeWalker { - constructor (root, f) { - this._filter = f || (() => true) - this._root = root - this._currentNode = root - this._firstCall = true - } - [Symbol.iterator] () { - return this - } - /** - * Get the next node. - * - * @return {YXmlElement} The next node. - */ - next () { - let n = this._currentNode - if (this._firstCall) { - this._firstCall = false - if (!n._deleted && this._filter(n)) { - return { value: n, done: false } - } - } - do { - if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) { - // walk down in the tree - n = n._start - } else { - // walk right or up in the tree - while (n !== this._root) { - if (n._right !== null) { - n = n._right - break - } - n = n._parent - } - if (n === this._root) { - n = null - } - } - if (n === this._root) { - break - } - } while (n !== null && (n._deleted || !this._filter(n))) - this._currentNode = n - if (n === null) { - return { done: true } - } else { - return { value: n, done: false } - } - } -} /** * Represents a list of {@link YXmlElement}. @@ -113,32 +28,6 @@ class YXmlTreeWalker { * Therefore it also must not be added as a childElement. */ export default class YXmlFragment extends YArray { - constructor () { - super() - this._dom = null - this._domFilter = defaultDomFilter - this._domObserver = null - // this function makes sure that either the - // dom event is executed, or the yjs observer is executed - var token = true - this._mutualExclude = f => { - if (token) { - token = false - try { - f() - } catch (e) { - console.error(e) - } - /* - if (this._domObserver !== null) { - this._domObserver.takeRecords() - } - */ - token = true - } - } - } - /** * Create a subtree of childNodes. * @@ -189,22 +78,6 @@ export default class YXmlFragment extends YArray { return Array.from(new YXmlTreeWalker(this, element => element.nodeName === query)) } - /** - * Enables the smart scrolling functionality for a Dom Binding. - * This is useful when YXml is bound to a shared editor. When activated, - * the viewport will be changed to accommodate remote changes. - * - * @TODO: Disabled for now. - * - * @param {Element} scrollElement The node that is - */ - enableSmartScrolling (scrollElement) { - this._scrollElement = scrollElement - this.forEach(xml => { - xml.enableSmartScrolling(scrollElement) - }) - } - /** * Dom filter function. * @@ -214,44 +87,12 @@ export default class YXmlFragment extends YArray { * @return {boolean} Whether to include the Dom node in the YXmlElement. */ - /** - * Filter out Dom elements. - * - * @param {domFilter} f The filtering function that decides whether to include - * a Dom node. - */ - setDomFilter (f) { - this._domFilter = f - let attributes = new Map() - if (this.getAttributes !== undefined) { - let attrs = this.getAttributes() - for (let key in attrs) { - attributes.set(key, attrs[key]) - } - } - this._y.transact(() => { - let result = this._domFilter(this.nodeName, new Map(attributes)) - if (result === null) { - this._delete(this._y) - } else { - attributes.forEach((value, key) => { - if (!result.has(key)) { - this.removeAttribute(key) - } - }) - } - this.forEach(xml => { - xml.setDomFilter(f) - }) - }) - } - /** * @private * Creates YArray Event and calls observers. */ _callObserver (transaction, parentSubs, remote) { - this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote)) + this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote, transaction)) } /** @@ -272,119 +113,20 @@ export default class YXmlFragment extends YArray { * Type was deleted. */ _delete (y, createDelete) { - this._unbindFromDom() super._delete(y, createDelete) } /** - * @private - * Unbind this YXmlFragment from the Dom. + * @return {DocumentFragment} The dom representation of this */ - _unbindFromDom () { - if (this._domObserver != null) { - this._domObserver.disconnect() - this._domObserver = null - } - if (this._dom != null) { - this._dom._yxml = null - this._dom = null - } - if (this._beforeTransactionHandler !== undefined) { - this._y.off('beforeTransaction', this._beforeTransactionHandler) - } - } - - /** - * Insert Dom Elements after one of the children of this YXmlFragment. - * The Dom elements will be bound to a new YXmlElement and inserted at the - * specified position. - * - * @param {YXmlElement|null} prev The reference node. New YxmlElements are - * inserted after this node. Set null to insert at - * the beginning. - * @param {Array} doms The Dom elements to insert. - * @param {?Document} _document Optional. Provide the global document object. - * @return {Array} The YxmlElements that are inserted. - */ - insertDomElementsAfter (prev, doms, _document) { - const types = domToYXml(this, doms, _document) - this.insertAfter(prev, types) - return types - } - - /** - * Insert Dom Elements at a specified index. - * The Dom elements will be bound to a new YXmlElement and inserted at the - * specified position. - * - * @param {Integer} index The position to insert elements at. - * @param {Array} doms The Dom elements to insert. - * @param {?Document} _document Optional. Provide the global document object. - * @return {Array} The YxmlElements that are inserted. - */ - insertDomElements (index, doms, _document) { - const types = domToYXml(this, doms, _document) - this.insert(index, types) - return types - } - - /** - * Get the Dom representation of this YXml type.. - */ - getDom () { - return this._dom - } - - /** - * Bind this YXmlFragment and all its children to a Dom Element. - * The content of the Dom Element are replaced with the Dom representation of - * the children of this YXml Type. - * - * @param {Element} dom The Dom Element that should be bound to this Type. - * @param {?Document} _document Optional. Provide the global document object. - */ - bindToDom (dom, _document) { - if (this._dom != null) { - this._unbindFromDom() - } - if (dom._yxml != null) { - dom._yxml._unbindFromDom() - } - dom.innerHTML = '' - this.forEach(t => { - dom.insertBefore(t.getDom(_document), null) + toDom (_document = document, binding) { + const fragment = _document.createDocumentFragment() + createAssociation(binding, fragment, this) + this.forEach(xmlType => { + fragment.insertBefore(xmlType.toDom(_document, binding), null) }) - this._bindToDom(dom, _document) + return fragment } - - /** - * @private - * Binds to a dom element. - * Only call if dom and YXml are isomorph - */ - _bindToDom (dom, _document) { - _document = _document || document - this._dom = dom - dom._yxml = this - if (this._parent === null) { - return - } - this._y.on('beforeTransaction', beforeTransactionSelectionFixer) - this._y.on('afterTransaction', afterTransactionSelectionFixer) - - this._y.on('beforeObserverCalls', function (y, transaction) { - // apply dom filter to new and changed types - transaction.changedTypes.forEach(function (subs, type) { - if (subs.size > 1 || !subs.has(null)) { - // only apply changes on attributes - applyFilter(type) - } - }) - transaction.newTypes.forEach(applyFilter) - }) - return dom - } - /** * @private * Transform this YXml Type to a readable format. diff --git a/src/Types/YXml/YXmlHook.js b/src/Types/YXml/YXmlHook.js index b492ab2d..f0fb0f52 100644 --- a/src/Types/YXml/YXmlHook.js +++ b/src/Types/YXml/YXmlHook.js @@ -7,16 +7,12 @@ import { getHook, addHook } from './hooks.js' * @param {String} hookName nodeName of the Dom Node. */ export default class YXmlHook extends YMap { - constructor (hookName, dom) { + constructor (hookName) { super() - this._dom = null this.hookName = null if (hookName !== undefined) { this.hookName = hookName - this._dom = dom dom._yjsHook = hookName - dom._yxml = this - getHook(hookName).fillType(dom, this) } } @@ -31,27 +27,14 @@ export default class YXmlHook extends YMap { } /** - * Returns the Dom representation of this YXmlHook. + * Creates a DOM element that represents this YXmlHook. + * + * @return Element The DOM representation of this Type. */ - getDom (_document) { - _document = _document || document - if (this._dom === null) { - const dom = getHook(this.hookName).createDom(this) - this._dom = dom - dom._yxml = this - dom._yjsHook = this.hookName - } - return this._dom - } - - /** - * @private - * Removes the Dom binding. - */ - _unbindFromDom () { - this._dom._yxml = null - this._yxml = null - // TODO: cleanup hook? + toDom (_document = document) { + const dom = getHook(this.hookName).createDom(this) + dom._yjsHook = this.hookName + return dom } /** @@ -99,11 +82,5 @@ export default class YXmlHook extends YMap { } super._integrate(y) } - setDomFilter () { - // TODO: implement new modfilter method! - } - enableSmartScrolling () { - // TODO: implement new smartscrolling method! - } } YXmlHook.addHook = addHook diff --git a/src/Types/YXml/YXmlText.js b/src/Types/YXml/YXmlText.js index 1320a350..19013356 100644 --- a/src/Types/YXml/YXmlText.js +++ b/src/Types/YXml/YXmlText.js @@ -1,4 +1,5 @@ import YText from '../YText/YText.js' +import { createAssociation } from '../../Bindings/DomBinding/util.js' /** * Represents text in a Dom Element. In the future this type will also handle @@ -7,93 +8,16 @@ import YText from '../YText/YText.js' * @param {String} arg1 Initial value. */ export default class YXmlText extends YText { - constructor (arg1) { - let dom = null - let initialText = null - if (arg1 != null) { - if (arg1.nodeType != null && arg1.nodeType === arg1.TEXT_NODE) { - dom = arg1 - initialText = dom.nodeValue - } else if (typeof arg1 === 'string') { - initialText = arg1 - } - } - super(initialText) - this._dom = null - this._domObserver = null - this._domObserverListener = null - this._scrollElement = null - if (dom !== null) { - this._setDom(arg1) - } - /* - var token = true - this._mutualExclude = f => { - if (token) { - token = false - try { - f() - } catch (e) { - console.error(e) - } - this._domObserver.takeRecords() - token = true - } - } - this.observe(event => { - if (this._dom != null) { - const dom = this._dom - this._mutualExclude(() => { - let anchorViewPosition = getAnchorViewPosition(this._scrollElement) - let anchorViewFix - if (anchorViewPosition !== null && (anchorViewPosition.anchor !== null || getBoundingClientRect(this._dom).top <= 0)) { - anchorViewFix = anchorViewPosition - } else { - anchorViewFix = null - } - dom.nodeValue = this.toString() - fixScrollPosition(this._scrollElement, anchorViewFix) - }) - } - }) - */ - } - setDomFilter () {} - enableSmartScrolling (scrollElement) { - this._scrollElement = scrollElement - } /** - * @private - * Set Dom element / Text Node that represents the same content as this - * YXmlElement. + * Creates a TextNode with the same textual content. * - * @param {Element} dom The Dom Element / Text Node that is set to be - * equivalent to this Type. + * @return TextNode */ - _setDom (dom) { - if (this._dom != null) { - this._unbindFromDom() - } - if (dom._yxml != null) { - dom._yxml._unbindFromDom() - } - // set marker - this._dom = dom - dom._yxml = this - } - - /** - * Returns the Dom representation of this YXmlText. - */ - getDom (_document) { - _document = _document || document - if (this._dom === null) { - const dom = _document.createTextNode(this.toString()) - this._setDom(dom) - return dom - } - return this._dom + toDom (_document = document, binding) { + const dom = _document.createTextNode(this.toString()) + createAssociation(binding, dom, this) + return dom } /** @@ -105,22 +29,6 @@ export default class YXmlText extends YText { * Type was deleted. */ _delete (y, createDelete) { - this._unbindFromDom() super._delete(y, createDelete) } - - /** - * @private - * Unbind this YXmlText from the Dom. - */ - _unbindFromDom () { - if (this._domObserver != null) { - this._domObserver.disconnect() - this._domObserver = null - } - if (this._dom != null) { - this._dom._yxml = null - this._dom = null - } - } } diff --git a/src/Types/YXml/domFilter.js b/src/Types/YXml/domFilter.js deleted file mode 100644 index ed992c53..00000000 --- a/src/Types/YXml/domFilter.js +++ /dev/null @@ -1,51 +0,0 @@ - -const filterMap = new Map() - -export function addFilter (type, filter) { - if (!filterMap.has(type)) { - filterMap.set(type, new Set()) - } - const filters = filterMap.get(type) - filters.add(filter) -} - -export function executeFilter (type) { - const y = type._y - let parent = type - const nodeName = type.nodeName - let attributes = new Map() - if (type.getAttributes !== undefined) { - let attrs = type.getAttributes() - for (let key in attrs) { - attributes.set(key, attrs[key]) - } - } - let filteredAttributes = new Map(attributes) - // is not y, supports dom filtering - while (parent !== y && parent.setDomFilter != null) { - const filters = filterMap.get(parent) - if (filters !== undefined) { - for (let f of filters) { - filteredAttributes = f(nodeName, filteredAttributes) - if (filteredAttributes === null) { - break - } - } - if (filteredAttributes === null) { - break - } - } - parent = parent._parent - } - if (filteredAttributes === null) { - type._delete(y) - } else { - // iterate original attributes - attributes.forEach((value, key) => { - // delete all attributes that are not in filteredAttributes - if (!filteredAttributes.has(key)) { - type.removeAttribute(key) - } - }) - } -} diff --git a/src/Types/YXml/utils.js b/src/Types/YXml/utils.js deleted file mode 100644 index f405d8a3..00000000 --- a/src/Types/YXml/utils.js +++ /dev/null @@ -1,189 +0,0 @@ -import { YXmlText, YXmlHook } from './YXml.js' - -export function defaultDomFilter (node, attributes) { - return attributes -} - -export function getAnchorViewPosition (scrollElement) { - if (scrollElement == null) { - return null - } - let anchor = document.getSelection().anchorNode - if (anchor != null) { - let top = getBoundingClientRect(anchor).top - if (top >= 0 && top <= document.documentElement.clientHeight) { - return { - anchor: anchor, - top: top - } - } - } - return { - anchor: null, - scrollTop: scrollElement.scrollTop, - scrollHeight: scrollElement.scrollHeight - } -} - -// get BoundingClientRect that works on text nodes -export function getBoundingClientRect (element) { - if (element.getBoundingClientRect != null) { - // is element node - return element.getBoundingClientRect() - } else { - // is text node - if (element.parentNode == null) { - // range requires that text nodes have a parent - let span = document.createElement('span') - span.appendChild(element) - } - let range = document.createRange() - range.selectNode(element) - return range.getBoundingClientRect() - } -} - -export function fixScrollPosition (scrollElement, fix) { - if (scrollElement !== null && fix !== null) { - if (fix.anchor === null) { - if (scrollElement.scrollTop === fix.scrollTop) { - scrollElement.scrollTop = scrollElement.scrollHeight - fix.scrollHeight - } - } else { - scrollElement.scrollTop = getBoundingClientRect(fix.anchor).top - fix.top - } - } -} - - -export function reflectChangesOnDom (events, _document) { - // Make sure that no filtered attributes are applied to the structure - // if they were, delete them - /* - events.forEach(event => { - const target = event.target - if (event.attributesChanged === undefined) { - // event.target is Y.XmlText - return - } - const keys = this._domFilter(target.nodeName, Array.from(event.attributesChanged)) - if (keys === null) { - target._delete() - } else { - const removeKeys = new Set() // is a copy of event.attributesChanged - event.attributesChanged.forEach(key => { removeKeys.add(key) }) - keys.forEach(key => { - // remove all accepted keys from removeKeys - removeKeys.delete(key) - }) - // remove the filtered attribute - removeKeys.forEach(key => { - target.removeAttribute(key) - }) - } - }) - */ - this._mutualExclude(() => { - events.forEach(event => { - const yxml = event.target - const dom = yxml._dom - if (dom != null) { - // TODO: do this once before applying stuff - // let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement) - if (yxml.constructor === YXmlText) { - yxml._dom.nodeValue = yxml.toString() - } else if (event.attributesChanged !== undefined) { - // update attributes - event.attributesChanged.forEach(attributeName => { - const value = yxml.getAttribute(attributeName) - if (value === undefined) { - dom.removeAttribute(attributeName) - } else { - dom.setAttribute(attributeName, value) - } - }) - /* - * TODO: instead of hard-checking the types, it would be best to - * specify the type's features. E.g. - * - _yxmlHasAttributes - * - _yxmlHasChildren - * Furthermore, the features shouldn't be encoded in the types, - * only in the attributes (above) - */ - if (event.childListChanged && yxml.constructor !== YXmlHook) { - let currentChild = dom.firstChild - yxml.forEach(function (t) { - let expectedChild = t.getDom(_document) - if (expectedChild.parentNode === dom) { - // is already attached to the dom. Look for it - while (currentChild !== expectedChild) { - let del = currentChild - currentChild = currentChild.nextSibling - dom.removeChild(del) - } - currentChild = currentChild.nextSibling - } else { - // this dom is not yet attached to dom - dom.insertBefore(expectedChild, currentChild) - } - }) - while (currentChild !== null) { - let tmp = currentChild.nextSibling - dom.removeChild(currentChild) - currentChild = tmp - } - } - } - /* TODO: smartscrolling - .. else if (event.type === 'childInserted' || event.type === 'insert') { - let nodes = event.values - for (let i = nodes.length - 1; i >= 0; i--) { - let node = nodes[i] - node.setDomFilter(yxml._domFilter) - node.enableSmartScrolling(yxml._scrollElement) - let dom = node.getDom() - let fixPosition = null - let nextDom = null - if (yxml._content.length > event.index + i + 1) { - nextDom = yxml.get(event.index + i + 1).getDom() - } - yxml._dom.insertBefore(dom, nextDom) - if (anchorViewPosition === null) { - // nop - } else if (anchorViewPosition.anchor !== null) { - // no scrolling when current selection - if (!dom.contains(anchorViewPosition.anchor) && !anchorViewPosition.anchor.contains(dom)) { - fixPosition = anchorViewPosition - } - } else if (getBoundingClientRect(dom).top <= 0) { - // adjust scrolling if modified element is out of view, - // there is no anchor element, and the browser did not adjust scrollTop (this is checked later) - fixPosition = anchorViewPosition - } - fixScrollPosition(yxml._scrollElement, fixPosition) - } - } else if (event.type === 'childRemoved' || event.type === 'delete') { - for (let i = event.values.length - 1; i >= 0; i--) { - let dom = event.values[i]._dom - let fixPosition = null - if (anchorViewPosition === null) { - // nop - } else if (anchorViewPosition.anchor !== null) { - // no scrolling when current selection - if (!dom.contains(anchorViewPosition.anchor) && !anchorViewPosition.anchor.contains(dom)) { - fixPosition = anchorViewPosition - } - } else if (getBoundingClientRect(dom).top <= 0) { - // adjust scrolling if modified element is out of view, - // there is no anchor element, and the browser did not adjust scrollTop (this is checked later) - fixPosition = anchorViewPosition - } - dom.remove() - fixScrollPosition(yxml._scrollElement, fixPosition) - } - } - */ - } - }) - }) -} diff --git a/src/Util/isParentOf.js b/src/Util/isParentOf.js new file mode 100644 index 00000000..76c3deab --- /dev/null +++ b/src/Util/isParentOf.js @@ -0,0 +1,18 @@ + +/** + * Check if `parent` is a parent of `child`. + * + * @param {Type} parent + * @param {Type} child + * @return {Boolean} Whether `parent` is a parent of `child`. + */ +export default function isParentOf (parent, child) { + child = child._parent + while (child !== null) { + if (child === parent) { + return true + } + child = child._parent + } + return false +} diff --git a/src/Y.dist.js b/src/Y.dist.js index e8d63022..92df64c7 100644 --- a/src/Y.dist.js +++ b/src/Y.dist.js @@ -14,8 +14,9 @@ import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from './Types/YXml/YXml import BinaryDecoder from './Util/Binary/Decoder.js' import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js' import { registerStruct } from './Util/structReferences.js' -import TextareaBinding from './Bindings/TextareaBinding.js' -import QuillBinding from './Bindings/QuillBinding.js' +import TextareaBinding from './Bindings/TextareaBinding/TextareaBinding.js' +import QuillBinding from './Bindings/QuillBinding/QuillBinding.js' +import DomBinding from './Bindings/DomBinding/DomBinding.js' import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js' import debug from 'debug' @@ -33,6 +34,7 @@ Y.XmlHook = YXmlHook Y.TextareaBinding = TextareaBinding Y.QuillBinding = QuillBinding +Y.DomBinding = DomBinding Y.utils = { BinaryDecoder, diff --git a/src/Y.js b/src/Y.js index e06dc940..ae81f937 100644 --- a/src/Y.js +++ b/src/Y.js @@ -6,6 +6,8 @@ import RootID from './Util/ID/RootID.js' import NamedEventHandler from './Util/NamedEventHandler.js' import Transaction from './Transaction.js' +export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js' + /** * A positive natural number including zero: 0, 1, 2, .. * @@ -44,7 +46,11 @@ export default class Y extends NamedEventHandler { } this._contentReady = false this._opts = opts - this.userID = generateUserID() + if (typeof opts.userID !== 'number') { + this.userID = generateUserID() + } else { + this.userID = opts.userID + } // TODO: This should be a Map so we can use encodables as keys this.share = {} this.ds = new DeleteStore(this) @@ -77,6 +83,8 @@ export default class Y extends NamedEventHandler { } else { initConnection() } + // for compatibility with isParentOf + this._parent = null } _setContentReady () { if (!this._contentReady) { diff --git a/test/y-xml.tests.js b/test/y-xml.tests.js index efe8d949..d168691c 100644 --- a/test/y-xml.tests.js +++ b/test/y-xml.tests.js @@ -3,14 +3,13 @@ import { test } from 'cutest' test('set property', async function xml0 (t) { var { users, xml0, xml1 } = await initArrays(t, { users: 2 }) - xml0.setAttribute('height', 10) - t.assert(xml0.getAttribute('height') === 10, 'Simple set+get works') + xml0.setAttribute('height', '10') + t.assert(xml0.getAttribute('height') === '10', 'Simple set+get works') await flushAll(t, users) - t.assert(xml1.getAttribute('height') === 10, 'Simple set+get works (remote)') + t.assert(xml1.getAttribute('height') === '10', 'Simple set+get works (remote)') await compareUsers(t, users) }) -/* TODO: Test YXml events! test('events', async function xml1 (t) { var { users, xml0, xml1 } = await initArrays(t, { users: 2 }) var event @@ -29,48 +28,28 @@ test('events', async function xml1 (t) { remoteEvent = e }) xml0.setAttribute('key', 'value') - expectedEvent = { - type: 'attributeChanged', - value: 'value', - name: 'key' - } - t.compare(event, expectedEvent, 'attribute changed event') + t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key') await flushAll(t, users) - t.compare(remoteEvent, expectedEvent, 'attribute changed event (remote)') + t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key (remote)') // check attributeRemoved xml0.removeAttribute('key') - expectedEvent = { - type: 'attributeRemoved', - name: 'key' - } - t.compare(event, expectedEvent, 'attribute deleted event') + t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute') await flushAll(t, users) - t.compare(remoteEvent, expectedEvent, 'attribute deleted event (remote)') - // test childInserted event - expectedEvent = { - type: 'childInserted', - index: 0 - } + t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute (remote)') xml0.insert(0, [new Y.XmlText('some text')]) - t.compare(event, expectedEvent, 'child inserted event') + t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element') await flushAll(t, users) - t.compare(remoteEvent, expectedEvent, 'child inserted event (remote)') + t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element (remote)') // test childRemoved xml0.delete(0) - expectedEvent = { - type: 'childRemoved', - index: 0 - } - t.compare(event, expectedEvent, 'child deleted event') + t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element') await flushAll(t, users) - t.compare(remoteEvent, expectedEvent, 'child deleted event (remote)') + t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)') await compareUsers(t, users) }) -*/ test('attribute modifications (y -> dom)', async function xml2 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.setAttribute('height', '100px') await wait() t.assert(dom0.getAttribute('height') === '100px', 'setAttribute') @@ -84,8 +63,7 @@ test('attribute modifications (y -> dom)', async function xml2 (t) { }) test('attribute modifications (dom -> y)', async function xml3 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.setAttribute('height', '100px') await wait() t.assert(xml0.getAttribute('height') === '100px', 'setAttribute') @@ -99,8 +77,7 @@ test('attribute modifications (dom -> y)', async function xml3 (t) { }) test('element insert (dom -> y)', async function xml4 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.insertBefore(document.createTextNode('some text'), null) dom0.insertBefore(document.createElement('p'), null) await wait() @@ -110,8 +87,7 @@ test('element insert (dom -> y)', async function xml4 (t) { }) test('element insert (y -> dom)', async function xml5 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('some text')]) xml0.insert(1, [new Y.XmlElement('p')]) t.assert(dom0.childNodes[0].textContent === 'some text', 'Retrieve Text node') @@ -120,8 +96,7 @@ test('element insert (y -> dom)', async function xml5 (t) { }) test('y on insert, then delete (dom -> y)', async function xml6 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.insertBefore(document.createElement('p'), null) await wait() t.assert(xml0.length === 1, 'one node present') @@ -132,8 +107,7 @@ test('y on insert, then delete (dom -> y)', async function xml6 (t) { }) test('y on insert, then delete (y -> dom)', async function xml7 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('p')]) t.assert(dom0.childNodes[0].nodeName === 'P', 'Get inserted element from dom') xml0.delete(0, 1) @@ -142,8 +116,7 @@ test('y on insert, then delete (y -> dom)', async function xml7 (t) { }) test('delete consecutive (1) (Text)', async function xml8 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')]) await wait() xml0.delete(1, 2) @@ -155,8 +128,7 @@ test('delete consecutive (1) (Text)', async function xml8 (t) { }) test('delete consecutive (2) (Text)', async function xml9 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')]) await wait() xml0.delete(0, 1) @@ -169,8 +141,7 @@ test('delete consecutive (2) (Text)', async function xml9 (t) { }) test('delete consecutive (1) (Element)', async function xml10 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) await wait() xml0.delete(1, 2) @@ -182,8 +153,7 @@ test('delete consecutive (1) (Element)', async function xml10 (t) { }) test('delete consecutive (2) (Element)', async function xml11 (t) { - var { users, xml0 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() + var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) await wait() xml0.delete(0, 1) @@ -196,9 +166,7 @@ test('delete consecutive (2) (Element)', async function xml11 (t) { }) test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() - let dom1 = xml1.getDom() + var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) users[1].disconnect() xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) xml0.insert(0, [new Y.XmlElement('X'), new Y.XmlElement('Y'), new Y.XmlElement('Z')]) @@ -212,9 +180,7 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { }) test('move element to a different position', async function xml13 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() - let dom1 = xml1.getDom() + var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) dom0.append(document.createElement('div')) dom0.append(document.createElement('h1')) await flushAll(t, users) @@ -227,9 +193,7 @@ test('move element to a different position', async function xml13 (t) { }) test('filter node', async function xml14 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() - let dom1 = xml1.getDom() + var { users, xml0, xml1, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { if (nodeName === 'H1') { return null @@ -237,8 +201,8 @@ test('filter node', async function xml14 (t) { return attrs } } - xml0.setDomFilter(domFilter) - xml1.setDomFilter(domFilter) + domBinding0.setFilter(domFilter) + domBinding1.setFilter(domFilter) dom0.append(document.createElement('div')) dom0.append(document.createElement('h1')) await flushAll(t, users) @@ -248,15 +212,13 @@ test('filter node', async function xml14 (t) { }) test('filter attribute', async function xml15 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() - let dom1 = xml1.getDom() + var { users, xml0, xml1, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { attrs.delete('hidden') return attrs } - xml0.setDomFilter(domFilter) - xml1.setDomFilter(domFilter) + domBinding0.setFilter(domFilter) + domBinding1.setFilter(domFilter) dom0.setAttribute('hidden', 'true') dom0.setAttribute('style', 'height: 30px') dom0.setAttribute('data-me', '77') @@ -269,9 +231,7 @@ test('filter attribute', async function xml15 (t) { }) test('deep element insert', async function xml16 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - let dom0 = xml0.getDom() - let dom1 = xml1.getDom() + var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) let deepElement = document.createElement('p') let boldElement = document.createElement('b') let attrElement = document.createElement('img') @@ -291,8 +251,8 @@ test('treeWalker', async function xml17 (t) { var { users, xml0 } = await initArrays(t, { users: 3 }) let paragraph1 = new Y.XmlElement('p') let paragraph2 = new Y.XmlElement('p') - let text1 = new Y.Text('init') - let text2 = new Y.Text('text') + let text1 = new Y.XmlText('init') + let text2 = new Y.XmlText('text') paragraph1.insert(0, [text1, text2]) xml0.insert(0, [paragraph1, paragraph2, new Y.XmlElement('img')]) let allParagraphs = xml0.querySelectorAll('p') @@ -309,8 +269,8 @@ test('treeWalker', async function xml17 (t) { * Incoming changes that contain malicious attributes should be deleted. */ test('Filtering remote changes', async function xmlFilteringRemote (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 3 }) - xml0.setDomFilter(function (nodeName, attributes) { + var { users, xml0, xml1, domBinding0 } = await initArrays(t, { users: 3 }) + domBinding0.setFilter(function (nodeName, attributes) { attributes.delete('malicious') if (nodeName === 'HIDEME') { return null @@ -320,10 +280,6 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) { return attributes } }) - // make sure that dom filters are active - // TODO: do not rely on .getDom for domFilters - xml0.getDom() - xml1.getDom() let paragraph = new Y.XmlElement('p') let hideMe = new Y.XmlElement('hideMe') let span = new Y.XmlElement('span') @@ -337,8 +293,8 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) { paragraph.insert(0, [tag2]) await flushAll(t, users) // check dom - paragraph.getDom().setAttribute('malicious', 'true') - span.getDom().setAttribute('malicious', 'true') + domBinding0.typeToDom.get(paragraph).setAttribute('malicious', 'true') + domBinding0.typeToDom.get(span).setAttribute('malicious', 'true') // check incoming attributes xml1.get(0).get(0).setAttribute('malicious', 'true') xml1.insert(0, [new Y.XmlElement('hideMe')]) @@ -350,35 +306,35 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) { // TODO: move elements var xmlTransactions = [ function attributeChange (t, user, chance) { - user.get('xml', Y.XmlElement).getDom().setAttribute(chance.word(), chance.word()) + user.dom.setAttribute(chance.word(), chance.word()) }, function attributeChangeHidden (t, user, chance) { - user.get('xml', Y.XmlElement).getDom().setAttribute('hidden', chance.word()) + user.dom.setAttribute('hidden', chance.word()) }, function insertText (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null dom.insertBefore(document.createTextNode(chance.word()), succ) }, function insertHiddenDom (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null dom.insertBefore(document.createElement('hidden'), succ) }, function insertDom (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null dom.insertBefore(document.createElement(chance.word()), succ) }, function deleteChild (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom if (dom.childNodes.length > 0) { var d = chance.pickone(dom.childNodes) d.remove() } }, function insertTextSecondLayer (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom if (dom.children.length > 0) { let dom2 = chance.pickone(dom.children) let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null @@ -386,7 +342,7 @@ var xmlTransactions = [ } }, function insertDomSecondLayer (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom if (dom.children.length > 0) { let dom2 = chance.pickone(dom.children) let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null @@ -394,7 +350,7 @@ var xmlTransactions = [ } }, function deleteChildSecondLayer (t, user, chance) { - let dom = user.get('xml', Y.XmlElement).getDom() + let dom = user.dom if (dom.children.length > 0) { let dom2 = chance.pickone(dom.children) if (dom2.childNodes.length > 0) { diff --git a/tests-lib/helper.js b/tests-lib/helper.js index a760ab19..d4bf1ea4 100644 --- a/tests-lib/helper.js +++ b/tests-lib/helper.js @@ -1,6 +1,7 @@ -import _Y from '../src/Y.js' -import yTest from './test-connector.js' +import _Y from '../src/Y.dist.js' +import { DomBinding } from '../src/Y.js' +import TestConnector from './test-connector.js' import Chance from 'chance' import ItemJSON from '../src/Struct/ItemJSON.js' @@ -10,11 +11,11 @@ import Quill from 'quill' export const Y = _Y -Y.extend(yTest) - export const database = { name: 'memory' } export const connector = { name: 'test', url: 'http://localhost:1234' } +Y.test = TestConnector + function getStateSet (y) { let ss = {} for (let [user, clock] of y.ss.state) { @@ -40,6 +41,7 @@ function getDeleteSet (y) { return ds } +// TODO: remove? export function attrsObject (dom) { let keys = [] let yxml = dom._yxml @@ -55,6 +57,7 @@ export function attrsObject (dom) { return obj } +// TODO: remove? export function domToJson (dom) { if (dom.nodeType === document.TEXT_NODE) { return dom.textContent @@ -140,6 +143,14 @@ export async function compareUsers (t, users) { users.map(u => u.destroy()) } +function domFilter (nodeName, attrs) { + if (nodeName === 'HIDDEN') { + return null + } + attrs.delete('hidden') + return attrs +} + export async function initArrays (t, opts) { var result = { users: [] @@ -154,27 +165,25 @@ export async function initArrays (t, opts) { connOpts = Object.assign({ role: 'slave' }, conn) } let y = new Y(connOpts.room, { - _userID: i, // evil hackery, don't try this at home + userID: i, // evil hackery, don't try this at home connector: connOpts }) result.users.push(y) result['array' + i] = y.define('array', Y.Array) result['map' + i] = y.define('map', Y.Map) - result['xml' + i] = y.define('xml', Y.XmlElement) + const yxml = y.define('xml', Y.XmlElement) + result['xml' + i] = yxml + const dom = document.createElement('my-dom') + const domBinding = new DomBinding(yxml, dom, { domFilter }) + result['domBinding' + i] = domBinding + result['dom' + i] = dom const textType = y.define('text', Y.Text) result['text' + i] = textType const quill = new Quill(document.createElement('div')) - const quillBinding = new Y.QuillBinding(textType, quill) + result['quillBinding' + i] = new Y.QuillBinding(textType, quill) result['quill' + i] = quill - result['quillBinding' + i] = quillBinding y.quill = quill // put quill on the y object (so we can use it later) - y.get('xml').setDomFilter(function (nodeName, attrs) { - if (nodeName === 'HIDDEN') { - return null - } - attrs.delete('hidden') - return attrs - }) + y.dom = dom y.on('afterTransaction', function () { for (let missing of y._missingStructs.values()) { if (Array.from(missing.values()).length > 0) { diff --git a/tests-lib/test-connector.js b/tests-lib/test-connector.js index ec24f71d..8cb0fdb6 100644 --- a/tests-lib/test-connector.js +++ b/tests-lib/test-connector.js @@ -1,6 +1,7 @@ /* global Y */ import { wait } from './helper' import { messageToString } from '../src/MessageHandler/messageToString' +import AbstractConnector from '../src/Connector.js' var rooms = {} @@ -64,107 +65,99 @@ function getTestRoom (roomname) { return rooms[roomname] } -export default function extendTestConnector (Y) { - class TestConnector extends Y.AbstractConnector { - constructor (y, options) { - if (options === undefined) { - throw new Error('Options must not be undefined!') - } - if (options.room == null) { - throw new Error('You must define a room name!') - } - options.forwardAppliedOperations = options.role === 'master' - super(y, options) - this.options = options - this.room = options.room - this.chance = options.chance - this.testRoom = getTestRoom(this.room) - this.testRoom.join(this) +export default class TestConnector extends AbstractConnector { + constructor (y, options) { + if (options === undefined) { + throw new Error('Options must not be undefined!') } - disconnect () { - this.testRoom.leave(this) - return super.disconnect() + if (options.room == null) { + throw new Error('You must define a room name!') } - logBufferParsed () { - console.log(' === Logging buffer of user ' + this.y.userID + ' === ') - for (let [user, conn] of this.connections) { - console.log(` ${user}:`) - for (let i = 0; i < conn.buffer.length; i++) { - console.log(messageToString(conn.buffer[i])) - } + options.forwardAppliedOperations = options.role === 'master' + super(y, options) + this.options = options + this.room = options.room + this.chance = options.chance + this.testRoom = getTestRoom(this.room) + this.testRoom.join(this) + } + disconnect () { + this.testRoom.leave(this) + return super.disconnect() + } + logBufferParsed () { + console.log(' === Logging buffer of user ' + this.y.userID + ' === ') + for (let [user, conn] of this.connections) { + console.log(` ${user}:`) + for (let i = 0; i < conn.buffer.length; i++) { + console.log(messageToString(conn.buffer[i])) } } - reconnect () { - this.testRoom.join(this) - super.reconnect() - return new Promise(resolve => { - this.whenSynced(resolve) - }) - } - send (uid, message) { - super.send(uid, message) - this.testRoom.send(this.y.userID, uid, message) - } - broadcast (message) { - super.broadcast(message) - this.testRoom.broadcast(this.y.userID, message) - } - async whenSynced (f) { - var synced = false - var periodicFlushTillSync = () => { - if (synced) { - f() - } else { - this.testRoom.flushAll([this.y]).then(function () { - setTimeout(periodicFlushTillSync, 10) - }) - } - } - periodicFlushTillSync() - return super.whenSynced(function () { - synced = true - }) - } - receiveMessage (sender, m) { - if (this.y.userID !== sender && this.connections.has(sender)) { - var buffer = this.connections.get(sender).buffer - if (buffer == null) { - buffer = this.connections.get(sender).buffer = [] - } - buffer.push(m) - if (this.chance.bool({likelihood: 30})) { - // flush 1/2 with 30% chance - var flushLength = Math.round(buffer.length / 2) - buffer.splice(0, flushLength).forEach(m => { - super.receiveMessage(sender, m) - }) - } + } + reconnect () { + this.testRoom.join(this) + super.reconnect() + return new Promise(resolve => { + this.whenSynced(resolve) + }) + } + send (uid, message) { + super.send(uid, message) + this.testRoom.send(this.y.userID, uid, message) + } + broadcast (message) { + super.broadcast(message) + this.testRoom.broadcast(this.y.userID, message) + } + async whenSynced (f) { + var synced = false + var periodicFlushTillSync = () => { + if (synced) { + f() + } else { + this.testRoom.flushAll([this.y]).then(function () { + setTimeout(periodicFlushTillSync, 10) + }) } } - async _flushAll (flushUsers) { - if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) { - // this one needs to sync with every other user - flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y) + periodicFlushTillSync() + return super.whenSynced(function () { + synced = true + }) + } + receiveMessage (sender, m) { + if (this.y.userID !== sender && this.connections.has(sender)) { + var buffer = this.connections.get(sender).buffer + if (buffer == null) { + buffer = this.connections.get(sender).buffer = [] } - for (let i = 0; i < flushUsers.length; i++) { - let userID = flushUsers[i].connector.y.userID - if (userID !== this.y.userID && this.connections.has(userID)) { - let buffer = this.connections.get(userID).buffer - if (buffer != null) { - var messages = buffer.splice(0) - for (let j = 0; j < messages.length; j++) { - super.receiveMessage(userID, messages[j]) - } + buffer.push(m) + if (this.chance.bool({likelihood: 30})) { + // flush 1/2 with 30% chance + var flushLength = Math.round(buffer.length / 2) + buffer.splice(0, flushLength).forEach(m => { + super.receiveMessage(sender, m) + }) + } + } + } + async _flushAll (flushUsers) { + if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) { + // this one needs to sync with every other user + flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y) + } + for (let i = 0; i < flushUsers.length; i++) { + let userID = flushUsers[i].connector.y.userID + if (userID !== this.y.userID && this.connections.has(userID)) { + let buffer = this.connections.get(userID).buffer + if (buffer != null) { + var messages = buffer.splice(0) + for (let j = 0; j < messages.length; j++) { + super.receiveMessage(userID, messages[j]) } } } - return 'done' } + return 'done' } - // TODO: this should be moved to a separate module (dont work on Y) - Y.test = TestConnector -} - -if (typeof Y !== 'undefined') { - extendTestConnector(Y) }