diff --git a/examples/quill/index.html b/examples/quill/index.html
index 4a8128b3..0f881d27 100644
--- a/examples/quill/index.html
+++ b/examples/quill/index.html
@@ -1,32 +1,18 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
diff --git a/examples/quill/index.js b/examples/quill/index.js
index f472cb7b..8e2ab6e9 100644
--- a/examples/quill/index.js
+++ b/examples/quill/index.js
@@ -1,40 +1,33 @@
/* global Y, Quill */
-// initialize a shared object. This function call returns a promise!
-
-Y({
- db: {
- name: 'memory'
- },
+let y = new Y('htmleditor10', {
connector: {
name: 'websockets-client',
- room: 'richtext-example-quill-1.0-test',
- url: 'http://localhost:1234'
- },
- sourceDir: '/bower_components',
- share: {
- richtext: 'Richtext' // y.share.richtext is of type Y.Richtext
+ url: 'http://127.0.0.1:1234'
}
-}).then(function (y) {
- window.yQuill = y
-
- // create quill element
- window.quill = new Quill('#quill', {
- modules: {
- formula: true,
- syntax: true,
- toolbar: [
- [{ size: ['small', false, 'large', 'huge'] }],
- ['bold', 'italic', 'underline'],
- [{ color: [] }, { background: [] }], // Snow theme fills in values
- [{ script: 'sub' }, { script: 'super' }],
- ['link', 'image'],
- ['link', 'code-block'],
- [{ list: 'ordered' }]
- ]
- },
- theme: 'snow'
- })
- // bind quill to richtext type
- y.share.richtext.bind(window.quill)
})
+
+let quill = new Quill('#quill-container', {
+ modules: {
+ toolbar: [
+ [{ header: [1, 2, false] }],
+ ['bold', 'italic', 'underline'],
+ ['image', 'code-block'],
+ [{ color: [] }, { background: [] }], // Snow theme fills in values
+ [{ script: 'sub' }, { script: 'super' }],
+ ['link', 'image'],
+ ['link', 'code-block'],
+ [{ list: 'ordered' }, { list: 'bullet' }]
+ ]
+ },
+ placeholder: 'Compose an epic...',
+ theme: 'snow' // or 'bubble'
+})
+
+let yText = y.define('quill', Y.Text)
+
+let quillBinding = new Y.QuillBinding(yText, quill)
+window.quillBinding = quillBinding
+window.yText = yText
+window.y = y
+window.quill = quill
diff --git a/package-lock.json b/package-lock.json
index d698c8a1..8bc1546c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1039,7 +1039,6 @@
"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",
@@ -1080,6 +1079,12 @@
"wordwrap": "0.0.2"
}
},
+ "clone": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
+ "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=",
+ "dev": true
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -1310,6 +1315,12 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
+ "deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+ "dev": true
+ },
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@@ -1777,6 +1788,12 @@
"es5-ext": "0.10.23"
}
},
+ "eventemitter3": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
+ "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=",
+ "dev": true
+ },
"exit-hook": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
@@ -1810,6 +1827,12 @@
"os-homedir": "1.0.2"
}
},
+ "extend": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
+ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
+ "dev": true
+ },
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -1834,6 +1857,12 @@
"integrity": "sha1-ysNCuPqJAm7+c6Jg/p9rgE9J5H8=",
"dev": true
},
+ "fast-diff": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
+ "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
+ "dev": true
+ },
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
@@ -1991,910 +2020,6 @@
"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.8.0",
- "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",
@@ -3843,13 +2968,6 @@
"integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
"dev": true
},
- "nan": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
- "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=",
- "dev": true,
- "optional": true
- },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -4012,6 +3130,12 @@
"p-limit": "1.1.0"
}
},
+ "parchment": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.3.tgz",
+ "integrity": "sha512-41Y+F8FejGa+URCuDTlS1zzzlYCwoZFTWpVwiQWDL82LFAAlIIiAo3JGJSLMiSPDeM3avFUivdXN3iY/i4mBXg==",
+ "dev": true
+ },
"parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
@@ -4199,6 +3323,31 @@
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true
},
+ "quill": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.5.tgz",
+ "integrity": "sha512-08P1DqKz4OZPJSlwSiZQxQ1a0F56+KEz6MttlpDNE42+WpjGuOyvsEQepScpdeyilHWrQwh61M5C1KelP8I8IA==",
+ "dev": true,
+ "requires": {
+ "clone": "2.1.1",
+ "deep-equal": "1.0.1",
+ "eventemitter3": "2.0.3",
+ "extend": "3.0.1",
+ "parchment": "1.1.3",
+ "quill-delta": "3.6.2"
+ }
+ },
+ "quill-delta": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.2.tgz",
+ "integrity": "sha512-grWEQq9woEidPDogtDNxQKmy2LFf9zBC0EU/YTSw6TwKmMjtihTxdnPtPRfrqazB2MSJ7YdCWxmsJ7aQKRSEgg==",
+ "dev": true,
+ "requires": {
+ "deep-equal": "1.0.1",
+ "extend": "3.0.1",
+ "fast-diff": "1.1.2"
+ }
+ },
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
diff --git a/package.json b/package.json
index d50e4c54..2e5cbd06 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"chance": "^1.0.9",
"concurrently": "^3.4.0",
"cutest": "^0.1.9",
+ "quill": "^1.3.5",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-inject": "^2.0.0",
diff --git a/rollup.browser.js b/rollup.browser.js
index 53149fdf..dfea0f74 100644
--- a/rollup.browser.js
+++ b/rollup.browser.js
@@ -20,7 +20,7 @@ export default {
}),
commonjs(),
babel(),
- uglify({
+ /*uglify({
mangle: {
except: ['YMap', 'Y', 'YArray', 'YText', 'YXmlHook', 'YXmlFragment', 'YXmlElement', 'YXmlEvent', 'YXmlText', 'YEvent', 'YArrayEvent', 'YMapEvent', 'Type', 'Delete', 'ItemJSON', 'ItemString', 'Item']
},
@@ -34,7 +34,7 @@ export default {
}
}
}
- })
+ })*/
],
banner: `
/**
diff --git a/rollup.test.js b/rollup.test.js
index f4072276..7e948a42 100644
--- a/rollup.test.js
+++ b/rollup.test.js
@@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry'
export default {
- input: 'test/index.js',
+ input: 'test/y-text.tests.js',
name: 'y-tests',
sourcemap: true,
output: {
diff --git a/src/Binary/Decoder.js b/src/Binary/Decoder.js
index 7208c4dd..fcc18b72 100644
--- a/src/Binary/Decoder.js
+++ b/src/Binary/Decoder.js
@@ -90,7 +90,7 @@ export default class BinaryDecoder {
for (let i = 0; i < len; i++) {
bytes[i] = this.uint8arr[this.pos++]
}
- let encodedString = String.fromCodePoint(...bytes)
+ let encodedString = bytes.map(b => String.fromCodePoint(b)).join('')
return decodeURIComponent(escape(encodedString))
}
/**
diff --git a/src/Binding/QuillBinding.js b/src/Binding/QuillBinding.js
new file mode 100644
index 00000000..8920f20f
--- /dev/null
+++ b/src/Binding/QuillBinding.js
@@ -0,0 +1,37 @@
+
+import Binding from './Binding.js'
+
+function typeObserver (event) {
+ const quill = this.target
+ quill.update('yjs')
+ this._mutualExclude(function () {
+ quill.updateContents(event.delta, 'yjs')
+ quill.update('yjs') // ignore applied changes
+ })
+}
+
+function quillObserver (delta) {
+ this._mutualExclude(() => {
+ this.type.applyDelta(delta.ops)
+ })
+}
+
+export default class QuillBinding extends Binding {
+ constructor (textType, quillInstance) {
+ // Binding handles textType as this.type and quillInstance as this.target
+ super(textType, quillInstance)
+ // set initial value
+ quillInstance.setContents(textType.toDelta(), 'yjs')
+ // Observers are handled by this class
+ this._typeObserver = typeObserver.bind(this)
+ this._quillObserver = quillObserver.bind(this)
+ textType.observe(this._typeObserver)
+ quillInstance.on('text-change', this._quillObserver)
+ }
+ destroy () {
+ // Remove everything that is handled by this class
+ this.type.unobserve(this._typeObserver)
+ this.target.unobserve(this._quillObserver)
+ super.destroy()
+ }
+}
diff --git a/src/Struct/Item.js b/src/Struct/Item.js
index 96a75cc8..d6fd420b 100644
--- a/src/Struct/Item.js
+++ b/src/Struct/Item.js
@@ -39,6 +39,11 @@ export function splitHelper (y, a, b, diff) {
o = o._right
}
y.os.put(b)
+ if (y._transaction.newTypes.has(a)) {
+ y._transaction.newTypes.add(b)
+ } else if (y._transaction.deletedStructs.has(a)) {
+ y._transaction.deletedStructs.add(b)
+ }
}
export default class Item {
diff --git a/src/Struct/ItemEmbed.js b/src/Struct/ItemEmbed.js
new file mode 100644
index 00000000..22c9fc2f
--- /dev/null
+++ b/src/Struct/ItemEmbed.js
@@ -0,0 +1,31 @@
+import { default as Item } from './Item.js'
+import { logID } from '../MessageHandler/messageToString.js'
+
+export default class ItemEmbed extends Item {
+ constructor () {
+ super()
+ this.embed = null
+ }
+ _copy (undeleteChildren, copyPosition) {
+ let struct = super._copy(undeleteChildren, copyPosition)
+ struct.embed = this.embed
+ return struct
+ }
+ get _length () {
+ return 1
+ }
+ _fromBinary (y, decoder) {
+ const missing = super._fromBinary(y, decoder)
+ this.embed = JSON.parse(decoder.readVarString())
+ return missing
+ }
+ _toBinary (encoder) {
+ super._toBinary(encoder)
+ encoder.writeVarString(JSON.stringify(this.embed))
+ }
+ _logString () {
+ const left = this._left !== null ? this._left._lastId : null
+ const origin = this._origin !== null ? this._origin._lastId : null
+ return `ItemEmbed(id:${logID(this._id)},embed:${JSON.stringify(this.embed)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})`
+ }
+}
diff --git a/src/Struct/ItemFormat.js b/src/Struct/ItemFormat.js
index de33f8f5..b384406b 100644
--- a/src/Struct/ItemFormat.js
+++ b/src/Struct/ItemFormat.js
@@ -1,7 +1,7 @@
import { default as Item } from './Item.js'
import { logID } from '../MessageHandler/messageToString.js'
-export default class ItemString extends Item {
+export default class ItemFormat extends Item {
constructor () {
super()
this.key = null
@@ -20,26 +20,19 @@ export default class ItemString extends Item {
return false
}
_fromBinary (y, decoder) {
- let missing = super._fromBinary(y, decoder)
+ const missing = super._fromBinary(y, decoder)
this.key = decoder.readVarString()
- this.value = decoder.readVarString()
+ this.value = JSON.parse(decoder.readVarString())
return missing
}
_toBinary (encoder) {
super._toBinary(encoder)
encoder.writeVarString(this.key)
- encoder.writeVarString(this.value)
+ encoder.writeVarString(JSON.stringify(this.value))
}
_logString () {
const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null
return `ItemFormat(id:${logID(this._id)},key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})`
}
- _splitAt (y, diff) {
- if (diff === 0) {
- return this
- } else {
- return this._right
- }
- }
}
diff --git a/src/Type/YArray.js b/src/Type/YArray.js
index 886af1af..90835566 100644
--- a/src/Type/YArray.js
+++ b/src/Type/YArray.js
@@ -4,12 +4,13 @@ import ItemString from '../Struct/ItemString.js'
import { logID } from '../MessageHandler/messageToString.js'
import YEvent from '../Util/YEvent.js'
-class YArrayEvent extends YEvent {
+export class YArrayEvent extends YEvent {
constructor (yarray, remote, transaction) {
super(yarray)
this.remote = remote
this._transaction = transaction
this._addedElements = null
+ this._removedElements = null
}
get addedElements () {
if (this._addedElements === null) {
@@ -26,15 +27,18 @@ class YArrayEvent extends YEvent {
return this._addedElements
}
get removedElements () {
- const target = this.target
- const transaction = this._transaction
- const removedElements = new Set()
- transaction.deletedStructs.forEach(function (struct) {
- if (struct._parent === target && !transaction.newTypes.has(struct)) {
- removedElements.add(struct)
- }
- })
- return removedElements
+ if (this._removedElements === null) {
+ const target = this.target
+ const transaction = this._transaction
+ const removedElements = new Set()
+ transaction.deletedStructs.forEach(function (struct) {
+ if (struct._parent === target && !transaction.newTypes.has(struct)) {
+ removedElements.add(struct)
+ }
+ })
+ this._removedElements = removedElements
+ }
+ return this._removedElements
}
}
diff --git a/src/Type/YText.js b/src/Type/YText.js
index 02650ea2..926d8ef7 100644
--- a/src/Type/YText.js
+++ b/src/Type/YText.js
@@ -1,7 +1,8 @@
import ItemString from '../Struct/ItemString.js'
+import ItemEmbed from '../Struct/ItemEmbed.js'
import ItemFormat from '../Struct/ItemFormat.js'
-import YArray from './YArray.js'
import { logID } from '../MessageHandler/messageToString.js'
+import { YArrayEvent, default as YArray } from './YArray.js'
function integrateItem (item, parent, y, left, right) {
item._origin = left
@@ -10,7 +11,7 @@ function integrateItem (item, parent, y, left, right) {
item._right_origin = right
item._parent = parent
if (y !== null) {
- item._integrate(this._y)
+ item._integrate(y)
} else if (left === null) {
parent._start = item
} else {
@@ -18,49 +19,365 @@ function integrateItem (item, parent, y, left, right) {
}
}
-function findPosition (parent, pos, attributes) {
- let currentAttributes = new Map()
- let left = null
- let right = parent._start
- let count = 0
- while (right !== null) {
+function findNextPosition (currentAttributes, parent, left, right, count) {
+ while (right !== null && count > 0) {
switch (right.constructor) {
- // case ItemBlockFormat: do not break..
+ case ItemEmbed:
case ItemString:
const rightLen = right._deleted ? 0 : (right._length - 1)
- if (count <= pos && pos <= count + rightLen) {
- const splitDiff = pos - count
- right = right._splitAt(parent._y, splitDiff)
+ if (count <= rightLen) {
+ right = right._splitAt(parent._y, count)
left = right._left
- count += splitDiff
- break
+ return [left, right, currentAttributes]
}
- if (!right._deleted) {
- count += right._length
+ if (right._deleted === false) {
+ count -= right._length
}
break
case ItemFormat:
if (right._deleted === false) {
- const key = right.key
- const value = right.value
- if (value === null) {
- currentAttributes.delete(key)
- } else if (attributes.hasOwnProperty(key)) {
- // only set if relevant
- currentAttributes.set(key, value)
- }
+ updateCurrentAttributes(currentAttributes, right)
}
break
}
left = right
right = right._right
}
- if (pos > count) {
- throw new Error('Position exceeds array range!')
- }
return [left, right, currentAttributes]
}
+function findPosition (parent, pos) {
+ let currentAttributes = new Map()
+ let left = null
+ let right = parent._start
+ return findNextPosition(currentAttributes, parent, left, right, pos)
+}
+
+// negate applied formats
+function insertNegatedAttributes (y, parent, left, right, negatedAttributes) {
+ // check if we really need to remove attributes
+ while (
+ right !== null && (
+ right._deleted === true || (
+ right.constructor === ItemFormat &&
+ (negatedAttributes.get(right.key) === right.value)
+ )
+ )
+ ) {
+ if (right._deleted === false) {
+ negatedAttributes.delete(right.key)
+ }
+ left = right
+ right = right._right
+ }
+ for (let [key, val] of negatedAttributes) {
+ let format = new ItemFormat()
+ format.key = key
+ format.value = val
+ integrateItem(format, parent, y, left, right)
+ left = format
+ }
+ return [left, right]
+}
+
+function updateCurrentAttributes (currentAttributes, item) {
+ const value = item.value
+ const key = item.key
+ if (value === null) {
+ currentAttributes.delete(key)
+ } else {
+ currentAttributes.set(key, value)
+ }
+}
+
+function minimizeAttributeChanges (left, right, currentAttributes, attributes) {
+ // go right while attributes[right.key] === right.value (or right is deleted)
+ while (true) {
+ if (right === null) {
+ break
+ } else if (right._deleted === true) {
+ // continue
+ } else if (right.constructor === ItemFormat && (attributes[right.key] || null) === right.value) {
+ // found a format, update currentAttributes and continue
+ updateCurrentAttributes(currentAttributes, right)
+ } else {
+ break
+ }
+ left = right
+ right = right._right
+ }
+ return [left, right]
+}
+
+function insertText (y, text, parent, left, right, currentAttributes, attributes) {
+ for (let [key] of currentAttributes) {
+ if (attributes.hasOwnProperty(key) === false) {
+ attributes[key] = null
+ }
+ }
+ [left, right] = minimizeAttributeChanges(left, right, currentAttributes, attributes)
+ let negatedAttributes = new Map()
+ // insert format-start items
+ for (let key in attributes) {
+ const val = attributes[key]
+ const currentVal = currentAttributes.get(key)
+ if (currentVal !== val) {
+ // save negated attribute (set null if currentVal undefined)
+ negatedAttributes.set(key, currentVal || null)
+ let format = new ItemFormat()
+ format.key = key
+ format.value = val
+ integrateItem(format, parent, y, left, right)
+ left = format
+ }
+ }
+ // insert content
+ let item
+ if (text.constructor === String) {
+ item = new ItemString()
+ item._content = text
+ } else {
+ item = new ItemEmbed()
+ item.embed = text
+ }
+ integrateItem(item, parent, y, left, right)
+ left = item
+ return insertNegatedAttributes(y, parent, left, right, negatedAttributes)
+}
+
+function formatText (y, length, parent, left, right, currentAttributes, attributes) {
+ [left, right] = minimizeAttributeChanges(left, right, currentAttributes, attributes)
+ let negatedAttributes = new Map()
+ // insert format-start items
+ for (let key in attributes) {
+ const val = attributes[key]
+ const currentVal = currentAttributes.get(key)
+ if (currentVal !== val) {
+ // save negated attribute (set null if currentVal undefined)
+ negatedAttributes.set(key, currentVal || null)
+ let format = new ItemFormat()
+ format.key = key
+ format.value = val
+ integrateItem(format, parent, y, left, right)
+ left = format
+ }
+ }
+ // iterate until first non-format or null is found
+ // delete all formats with attributes[format.key] != null
+ while (length > 0 && right !== null) {
+ if (right._deleted === false) {
+ switch (right.constructor) {
+ case ItemFormat:
+ if (attributes.hasOwnProperty(right.key)) {
+ if (attributes[right.key] === right.value) {
+ negatedAttributes.delete(right.key)
+ } else {
+ negatedAttributes.set(right.key, right.value)
+ }
+ right._delete(y)
+ }
+ updateCurrentAttributes(currentAttributes, right)
+ break
+ case ItemEmbed:
+ case ItemString:
+ right._splitAt(y, length)
+ length -= right._length
+ break
+ }
+ }
+ left = right
+ right = right._right
+ }
+ return insertNegatedAttributes(y, parent, left, right, negatedAttributes)
+}
+
+function deleteText (y, length, parent, left, right, currentAttributes) {
+ while (length > 0 && right !== null) {
+ if (right._deleted === false) {
+ switch (right.constructor) {
+ case ItemFormat:
+ updateCurrentAttributes(currentAttributes, right)
+ break
+ case ItemEmbed:
+ case ItemString:
+ right._splitAt(y, length)
+ length -= right._length
+ right._delete(y)
+ break
+ }
+ }
+ left = right
+ right = right._right
+ }
+ return [left, right]
+}
+
+class YTextEvent extends YArrayEvent {
+ constructor (ytext, remote, transaction) {
+ super(ytext, remote, transaction)
+ this._delta = null
+ }
+ get delta () {
+ if (this._delta === null) {
+ const y = this.target._y
+ y.transact(() => {
+ let item = this.target._start
+ const delta = []
+ const added = this.addedElements
+ const removed = this.removedElements
+ this._delta = delta
+ let action = null
+ let attributes = {} // counts added or removed new attributes for retain
+ const currentAttributes = new Map() // saves all current attributes for insert
+ const oldAttributes = new Map()
+ let insert = ''
+ let retain = 0
+ let deleteLen = 0
+ const addOp = function addOp () {
+ if (action !== null) {
+ let op
+ switch (action) {
+ case 'delete':
+ op = { delete: deleteLen }
+ deleteLen = 0
+ break
+ case 'insert':
+ op = { insert }
+ if (currentAttributes.size > 0) {
+ op.attributes = {}
+ for (let [key, value] of currentAttributes) {
+ if (value !== null) {
+ op.attributes[key] = value
+ }
+ }
+ }
+ insert = ''
+ break
+ case 'retain':
+ op = { retain }
+ if (Object.keys(attributes).length > 0) {
+ op.attributes = {}
+ for (let key in attributes) {
+ op.attributes[key] = attributes[key]
+ }
+ }
+ retain = 0
+ break
+ }
+ delta.push(op)
+ action = null
+ }
+ }
+ while (item !== null) {
+ switch (item.constructor) {
+ case ItemEmbed:
+ if (added.has(item)) {
+ addOp()
+ action = 'insert'
+ insert = item.embed
+ addOp()
+ } else if (removed.has(item)) {
+ if (action !== 'delete') {
+ addOp()
+ action = 'delete'
+ }
+ deleteLen += 1
+ } else if (item._deleted === false) {
+ if (action !== 'retain') {
+ addOp()
+ action = 'retain'
+ }
+ retain += 1
+ }
+ break
+ case ItemString:
+ if (added.has(item)) {
+ if (action !== 'insert') {
+ addOp()
+ action = 'insert'
+ }
+ insert += item._content
+ } else if (removed.has(item)) {
+ if (action !== 'delete') {
+ addOp()
+ action = 'delete'
+ }
+ deleteLen += item._length
+ } else if (item._deleted === false) {
+ if (action !== 'retain') {
+ addOp()
+ action = 'retain'
+ }
+ retain += item._length
+ }
+ break
+ case ItemFormat:
+ if (added.has(item)) {
+ const curVal = currentAttributes.get(item.key) || null
+ if (curVal !== item.value) {
+ if (action === 'retain') {
+ addOp()
+ }
+ if (item.value === (oldAttributes.get(item.key) || null)) {
+ delete attributes[item.key]
+ } else {
+ attributes[item.key] = item.value
+ }
+ } else {
+ item._delete(y)
+ }
+ } else if (removed.has(item)) {
+ oldAttributes.set(item.key, item.value)
+ const curVal = currentAttributes.get(item.key) || null
+ if (curVal !== item.value) {
+ if (action === 'retain') {
+ addOp()
+ }
+ attributes[item.key] = curVal
+ }
+ } else if (item._deleted === false) {
+ oldAttributes.set(item.key, item.value)
+ if (attributes.hasOwnProperty(item.key)) {
+ if (attributes[item.key] !== item.value) {
+ if (action === 'retain') {
+ addOp()
+ }
+ if (item.value === null) {
+ attributes[item.key] = item.value
+ } else {
+ delete attributes[item.key]
+ }
+ } else {
+ item._delete(y)
+ }
+ }
+ }
+ if (item._deleted === false) {
+ if (action === 'insert') {
+ addOp()
+ }
+ updateCurrentAttributes(currentAttributes, item)
+ }
+ break
+ }
+ item = item._right
+ }
+ addOp()
+ while (this._delta.length > 0) {
+ let lastOp = this._delta[this._delta.length - 1]
+ if (lastOp.hasOwnProperty('retain') && !lastOp.hasOwnProperty('attributes')) {
+ // retain delta's if they don't assign attributes
+ this._delta.pop()
+ } else {
+ break
+ }
+ }
+ })
+ }
+ return this._delta
+ }
+}
+
export default class YText extends YArray {
constructor (string) {
super()
@@ -71,6 +388,9 @@ export default class YText extends YArray {
this._start = start
}
}
+ _callObserver (transaction, parentSubs, remote) {
+ this._callEventHandler(transaction, new YTextEvent(this, remote, transaction))
+ }
toString () {
let str = ''
let n = this._start
@@ -82,10 +402,27 @@ export default class YText extends YArray {
}
return str
}
+ applyDelta (delta) {
+ this._transact(y => {
+ let left = null
+ let right = this._start
+ const currentAttributes = new Map()
+ for (let i = 0; i < delta.length; i++) {
+ let op = delta[i]
+ if (op.hasOwnProperty('insert')) {
+ ;[left, right] = insertText(y, op.insert, this, left, right, currentAttributes, op.attributes || {})
+ } else if (op.hasOwnProperty('retain')) {
+ ;[left, right] = formatText(y, op.retain, this, left, right, currentAttributes, op.attributes || {})
+ } else if (op.hasOwnProperty('delete')) {
+ ;[left, right] = deleteText(y, op.delete, this, left, right, currentAttributes)
+ }
+ }
+ })
+ }
/**
* As defined by Quilljs - https://quilljs.com/docs/delta/
*/
- toRichtextDelta () {
+ toDelta () {
let ops = []
let currentAttributes = new Map()
let str = ''
@@ -94,10 +431,16 @@ export default class YText extends YArray {
if (str.length > 0) {
// pack str with attributes to ops
let attributes = {}
+ let addAttributes = false
for (let [key, value] of currentAttributes) {
+ addAttributes = true
attributes[key] = value
}
- ops.push({ insert: str, attributes })
+ let op = { insert: str }
+ if (addAttributes) {
+ op.attributes = attributes
+ }
+ ops.push(op)
str = ''
}
}
@@ -109,13 +452,7 @@ export default class YText extends YArray {
break
case ItemFormat:
packStr()
- const value = n.value
- const key = n.key
- if (value === null) {
- currentAttributes.delete(key)
- } else {
- currentAttributes.set(key, value)
- }
+ updateCurrentAttributes(currentAttributes, n)
break
}
}
@@ -129,72 +466,35 @@ export default class YText extends YArray {
return
}
this._transact(y => {
- let [left, right, currentAttributes] = findPosition(this, pos, attributes)
- let negatedAttributes = new Map()
- // insert format-start items
- for (let key in attributes) {
- const val = attributes[key]
- const currentVal = currentAttributes.get(key)
- if (currentVal !== val) {
- // save negated attribute (set null if currentVal undefined)
- negatedAttributes.set(key, currentVal || null)
- let format = new ItemFormat()
- format.key = key
- format.value = val
- integrateItem(format, this, y, left, right)
- left = format
- }
- }
- // insert text content
- let item = new ItemString()
- item._content = text
- integrateItem(item, this, y, left, right)
- left = item
- // negate applied formats
- for (let [key, value] of negatedAttributes) {
- let format = new ItemFormat()
- format.key = key
- format.value = value
- integrateItem(format, this, y, left, right)
- left = format
- }
+ let [left, right, currentAttributes] = findPosition(this, pos)
+ insertText(y, text, this, left, right, currentAttributes, attributes)
+ })
+ }
+ insertEmbed (pos, embed, attributes = {}) {
+ if (embed.constructor !== Object) {
+ throw new Error('Embed must be an Object')
+ }
+ this._transact(y => {
+ let [left, right, currentAttributes] = findPosition(this, pos)
+ insertText(y, embed, this, left, right, currentAttributes, attributes)
+ })
+ }
+ delete (pos, length) {
+ if (length === 0) {
+ return
+ }
+ this._transact(y => {
+ let [left, right, currentAttributes] = findPosition(this, pos)
+ deleteText(y, length, this, left, right, currentAttributes)
})
}
format (pos, length, attributes) {
this._transact(y => {
- let [left, _right, currentAttributes] = findPosition(this, pos, attributes)
- if (_right === null) {
+ let [left, right, currentAttributes] = findPosition(this, pos)
+ if (right === null) {
return
}
- let negatedAttributes = new Map()
- // insert format-start items
- for (let key in attributes) {
- const val = attributes[key]
- const currentVal = currentAttributes.get(key)
- if (currentVal !== val) {
- // save negated attribute (set null if currentVal undefined)
- negatedAttributes.set(key, currentVal || null)
- let format = new ItemFormat()
- format.key = key
- format.value = val
- integrateItem(format, this, y, left, _right)
- left = format
- }
- }
- // iterate until first non-format or null is found
- // delete all formats with attributes[format.key] != null
- while (length > 0 && left !== null) {
- if (left._deleted === false) {
- if (left.constructor === ItemFormat) {
- if (attributes[left.key] != null) {
- left.delete(y)
- }
- } else if (length < left._length) {
-
- }
- }
- left = left._right
- }
+ formatText(y, length, this, left, right, currentAttributes, attributes)
})
}
_logString () {
diff --git a/src/Util/structReferences.js b/src/Util/structReferences.js
index 53d82f48..9a09c7a0 100644
--- a/src/Util/structReferences.js
+++ b/src/Util/structReferences.js
@@ -6,6 +6,8 @@ import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from '../Type/y-xml/y-x
import Delete from '../Struct/Delete.js'
import ItemJSON from '../Struct/ItemJSON.js'
import ItemString from '../Struct/ItemString.js'
+import ItemFormat from '../Struct/ItemFormat.js'
+import ItemEmbed from '../Struct/ItemEmbed.js'
const structs = new Map()
const references = new Map()
@@ -23,8 +25,11 @@ export function getReference (typeConstructor) {
return references.get(typeConstructor)
}
+// TODO: reorder (Item* should have low numbers)
addStruct(0, ItemJSON)
addStruct(1, ItemString)
+addStruct(10, ItemFormat)
+addStruct(11, ItemEmbed)
addStruct(2, Delete)
addStruct(3, YArray)
diff --git a/src/Y.js b/src/Y.js
index 568e36ff..fd29a6e9 100644
--- a/src/Y.js
+++ b/src/Y.js
@@ -23,6 +23,7 @@ import debug from 'debug'
import Transaction from './Transaction.js'
import TextareaBinding from './Binding/TextareaBinding.js'
+import QuillBinding from './Binding/QuillBinding.js'
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
@@ -202,6 +203,7 @@ Y.XmlText = YXmlText
Y.XmlHook = YXmlHook
Y.TextareaBinding = TextareaBinding
+Y.QuillBinding = QuillBinding
Y.utils = {
BinaryDecoder,
diff --git a/test/y-text.tests.js b/test/y-text.tests.js
new file mode 100644
index 00000000..4b73ec11
--- /dev/null
+++ b/test/y-text.tests.js
@@ -0,0 +1,102 @@
+import { initArrays, compareUsers, flushAll } from '../tests-lib/helper.js'
+import { test, proxyConsole } from 'cutest'
+
+proxyConsole()
+
+test('basic insert delete', async function text0 (t) {
+ let { users, text0 } = await initArrays(t, { users: 2 })
+ let delta
+
+ text0.observe(function (event) {
+ delta = event.delta
+ })
+
+ text0.delete(0, 0)
+ t.assert(true, 'Does not throw when deleting zero elements with position 0')
+
+ text0.insert(0, 'abc')
+ t.assert(text0.toString() === 'abc', 'Basic insert works')
+ t.compare(delta, [{ insert: 'abc' }])
+
+ text0.delete(0, 1)
+ t.assert(text0.toString() === 'bc', 'Basic delete works (position 0)')
+ t.compare(delta, [{ delete: 1 }])
+
+ text0.delete(1, 1)
+ t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
+ t.compare(delta, [{ retain: 1 }, { delete: 1 }])
+
+ await compareUsers(t, users)
+})
+
+test('basic format', async function text1 (t) {
+ let { users, text0 } = await initArrays(t, { users: 2 })
+ let delta
+ text0.observe(function (event) {
+ delta = event.delta
+ })
+ text0.insert(0, 'abc', { bold: true })
+ t.assert(text0.toString() === 'abc', 'Basic insert with attributes works')
+ t.compare(text0.toDelta(), [{ insert: 'abc', attributes: { bold: true } }])
+ t.compare(delta, [{ insert: 'abc', attributes: { bold: true } }])
+ text0.delete(0, 1)
+ t.assert(text0.toString() === 'bc', 'Basic delete on formatted works (position 0)')
+ t.compare(text0.toDelta(), [{ insert: 'bc', attributes: { bold: true } }])
+ t.compare(delta, [{ delete: 1 }])
+ text0.delete(1, 1)
+ t.assert(text0.toString() === 'b', 'Basic delete works (position 1)')
+ t.compare(text0.toDelta(), [{ insert: 'b', attributes: { bold: true } }])
+ t.compare(delta, [{ retain: 1 }, { delete: 1 }])
+ text0.insert(0, 'z', {bold: true})
+ t.assert(text0.toString() === 'zb')
+ t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }])
+ t.compare(delta, [{ insert: 'z', attributes: { bold: true } }])
+ t.assert(text0._start._right._right._right._content === 'b', 'Does not insert duplicate attribute marker')
+ text0.insert(0, 'y')
+ t.assert(text0.toString() === 'yzb')
+ t.compare(text0.toDelta(), [{ insert: 'y' }, { insert: 'zb', attributes: { bold: true } }])
+ t.compare(delta, [{ insert: 'y' }])
+ text0.format(0, 2, { bold: null })
+ t.assert(text0.toString() === 'yzb')
+ t.compare(text0.toDelta(), [{ insert: 'yz' }, { insert: 'b', attributes: { bold: true } }])
+ t.compare(delta, [{ retain: 1 }, { retain: 1, attributes: { bold: null } }])
+ await compareUsers(t, users)
+})
+
+test('quill issue 1', async function quill1 (t) {
+ let { users, quill0 } = await initArrays(t, { users: 2 })
+ quill0.insertText(0, 'x')
+ await flushAll(t, users)
+ quill0.insertText(1, '\n', 'list', 'ordered')
+ await flushAll(t, users)
+ quill0.insertText(1, '\n', 'list', 'ordered')
+ await compareUsers(t, users)
+})
+
+test('quill issue 2', async function quill2 (t) {
+ let { users, quill0, text0 } = await initArrays(t, { users: 2 })
+ let delta
+ text0.observe(function (event) {
+ delta = event.delta
+ })
+ quill0.insertText(0, 'abc', 'bold', true)
+ await flushAll(t, users)
+ quill0.insertText(1, 'x')
+ quill0.update()
+ t.compare(delta, [{ retain: 1 }, { insert: 'x', attributes: { bold: true } }])
+ await compareUsers(t, users)
+})
+
+test('quill issue 3', async function quill3 (t) {
+ let { users, quill0, text0 } = await initArrays(t, { users: 2 })
+ quill0.insertText(0, 'a')
+ quill0.insertText(1, '\n\n', 'list', 'ordered')
+ quill0.insertText(2, 'b')
+ t.compare(text0.toDelta(), [
+ { insert: 'a' },
+ { insert: '\n', attributes: { list: 'ordered' } },
+ { insert: 'b' },
+ { insert: '\n', attributes: { list: 'ordered' } }
+ ])
+ await compareUsers(t, users)
+})
diff --git a/tests-lib/helper.js b/tests-lib/helper.js
index ebacea27..a760ab19 100644
--- a/tests-lib/helper.js
+++ b/tests-lib/helper.js
@@ -6,6 +6,7 @@ import Chance from 'chance'
import ItemJSON from '../src/Struct/ItemJSON.js'
import ItemString from '../src/Struct/ItemString.js'
import { defragmentItemContent } from '../src/Util/defragmentItemContent.js'
+import Quill from 'quill'
export const Y = _Y
@@ -92,9 +93,14 @@ export async function compareUsers (t, users) {
await wait()
await flushAll(t, users)
- var userArrayValues = users.map(u => u.get('array', Y.Array).toJSON().map(val => JSON.stringify(val)))
- var userMapValues = users.map(u => u.get('map', Y.Map).toJSON())
- var userXmlValues = users.map(u => u.get('xml', Y.Xml).toString())
+ var userArrayValues = users.map(u => u.define('array', Y.Array).toJSON().map(val => JSON.stringify(val)))
+ var userMapValues = users.map(u => u.define('map', Y.Map).toJSON())
+ var userXmlValues = users.map(u => u.define('xml', Y.Xml).toString())
+ var userTextValues = users.map(u => u.define('text', Y.Text).toDelta())
+ var userQuillValues = users.map(u => {
+ u.quill.update('yjs') // get latest changes
+ return u.quill.getContents().ops
+ })
var data = users.map(u => {
defragmentItemContent(u)
@@ -124,6 +130,8 @@ export async function compareUsers (t, users) {
t.compare(userArrayValues[i], userArrayValues[i + 1], 'array types')
t.compare(userMapValues[i], userMapValues[i + 1], 'map types')
t.compare(userXmlValues[i], userXmlValues[i + 1], 'xml types')
+ t.compare(userTextValues[i], userTextValues[i + 1], 'text types')
+ t.compare(userQuillValues[i], userQuillValues[i + 1], 'quill delta content')
t.compare(data[i].os, data[i + 1].os, 'os')
t.compare(data[i].ds, data[i + 1].ds, 'ds')
t.compare(data[i].ss, data[i + 1].ss, 'ss')
@@ -153,6 +161,13 @@ export async function initArrays (t, opts) {
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 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['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