diff --git a/package-lock.json b/package-lock.json index ccecef0c..f92e77a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.4.7", + "version": "13.4.12", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -31,59 +31,95 @@ } }, "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", "dev": true }, "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.0.0.tgz", + "integrity": "sha512-/omBIJG1nHQc+bgkYDuLpb/V08QyutP9amOrJRUSlYJZP+b/68gM//D8sxJe3Yry2QnYIr3QjR3x4AlxJEN3GA==", "dev": true, "requires": { - "@rollup/pluginutils": "^3.0.8", + "@rollup/pluginutils": "^3.1.0", "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + } } }, "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.0.1.tgz", + "integrity": "sha512-ltlsj/4Bhwwhb+Nb5xCz/6vieuEj2/BAkkqVIKmZwC7pIdl8srmgmglE4S0jFlZa32K4qvdQ6NHdmpRKD/LwoQ==", "dev": true, "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", "is-module": "^1.0.0", - "resolve": "^1.14.2" + "resolve": "^1.19.0" }, "dependencies": { "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } } } }, "@rollup/pluginutils": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.10.tgz", - "integrity": "sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, "requires": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } } }, "@types/color-name": { @@ -99,15 +135,15 @@ "dev": true }, "@types/node": { - "version": "14.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.9.tgz", - "integrity": "sha512-0sCTiXKXELOBxvZLN4krQ0FPOAA7ij+6WwvD0k/PHd9/KAkr4dXel5J9fh6F4x1FwAQILqAWkmpeuS6mjf1iKA==", + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", "dev": true }, "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", "dev": true, "requires": { "@types/node": "*" @@ -239,9 +275,9 @@ } }, "builtin-modules": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", - "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", "dev": true }, "callsites": { @@ -458,6 +494,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -946,9 +988,9 @@ "dev": true }, "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, "esutils": { @@ -1057,6 +1099,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1243,9 +1292,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "inquirer": { @@ -1342,6 +1391,15 @@ "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", "dev": true }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -1376,20 +1434,12 @@ "dev": true }, "is-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.0.tgz", - "integrity": "sha512-ZVxq+5TkOx6GQdnoMm2aRdCKADdcrOWXLGzGT+vIA8DMpqEJaRk5AL1bS80zJ2bjHunVmjdzfCt0e4BymIEqKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, "requires": { - "@types/estree": "0.0.44" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.44", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", - "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==", - "dev": true - } + "@types/estree": "*" } }, "is-regex": { @@ -1429,9 +1479,9 @@ "dev": true }, "isomorphic.js": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.4.tgz", - "integrity": "sha512-t9zbgkjE7f9f2M6OSW49YEq0lUrSdAllBbWFUZoeck/rnnFae6UlhmDtXWs48VJY3ZpryCoZsRiAiKD44hPIGQ==" + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.5.tgz", + "integrity": "sha512-MkX5lLQApx/8IAIU31PKvpAZosnu2Jqcj1rM8TzxyA4CR96tv3SgMKQNTCxL58G7696Q57zd7ubHV/hTg+5fNA==" }, "js-tokens": { "version": "4.0.0", @@ -1459,9 +1509,9 @@ } }, "jsdoc": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.5.tgz", - "integrity": "sha512-SbY+i9ONuxSK35cgVHaI8O9senTE4CDYAmGSDJ5l3+sfe62Ff4gy96osy6OW84t4K4A8iGnMrlRrsSItSNp3RQ==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz", + "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==", "dev": true, "requires": { "@babel/parser": "^7.9.4", @@ -1548,9 +1598,9 @@ } }, "lib0": { - "version": "0.2.33", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.33.tgz", - "integrity": "sha512-Pnm8FzjUr+aTYkEu2A20c1EfVHla8GbVX+GXn6poxx0gcmEuCs+XszjLmtEbI9xYOoI/83xVi7VOIoyHgOO87w==", + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.35.tgz", + "integrity": "sha512-drVD3EscB3TIxiFzceuZg7oF5Z6I8a0KX+7FowNcAXOEsTej/hlHB+ElJ8Pa/Ge73Gy3fklSJtPxpNd2PajdWg==", "requires": { "isomorphic.js": "^0.1.3" } @@ -1587,9 +1637,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.assignin": { @@ -2380,22 +2430,14 @@ } }, "rollup": { - "version": "1.32.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", - "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "version": "2.36.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.36.1.tgz", + "integrity": "sha512-eAfqho8dyzuVvrGqpR0ITgEdq0zG2QJeWYh+HeuTbpcaXk8vNFc48B7bJa1xYosTCKx0CuW+447oQOW8HgBIZQ==", "dev": true, "requires": { - "@types/estree": "*", - "@types/node": "*", - "acorn": "^7.1.0" + "fsevents": "~2.1.2" } }, - "rollup-cli": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/rollup-cli/-/rollup-cli-1.0.9.tgz", - "integrity": "sha1-N/ShwgYxHikuMpfql3eduKIduZQ=", - "dev": true - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -2898,12 +2940,12 @@ "dev": true }, "y-protocols": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.2.3.tgz", - "integrity": "sha512-mJ838iW7XgMQqlv+9DtH7QyLqflZoy/VvaUWRIpwawee4mQiFJcEXazCmSYUHEbXIUuVNNc70FnuNSMWDC5vKQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.2.tgz", + "integrity": "sha512-V6ZAmokdogW52+VsIg/YC0R6CHWgG8/hjO3rYL10hAzeT5j464kDiRki31O+GzTj+dMgNYZNd6IDP9X35FLXrw==", "dev": true, "requires": { - "lib0": "^0.2.20" + "lib0": "^0.2.35" } } } diff --git a/package.json b/package.json index 3b265715..8fb41553 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.4.7", + "version": "13.4.12", "description": "Shared Editing Library", "main": "./dist/yjs.cjs", "module": "./dist/yjs.mjs", @@ -23,16 +23,14 @@ "debug": "concurrently 'http-server -o test.html' 'npm run watch'", "trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs", "trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs", - "postinstall": "node ./funding.cjs" + "postinstall": "node ./sponsor-y.js" }, "files": [ - "dist/*", - "src/*", - "tests/*", - "docs/*" + "dist/yjs.*", + "dist/src", + "sponsor-y.js" ], "dictionaries": { - "doc": "docs", "test": "tests" }, "standard": { @@ -60,22 +58,21 @@ "bugs": { "url": "https://github.com/yjs/yjs/issues" }, - "homepage": "https://yjs.dev", + "homepage": "https://docs.yjs.dev", "dependencies": { - "lib0": "^0.2.33" + "lib0": "^0.2.35" }, "devDependencies": { - "@rollup/plugin-commonjs": "^11.1.0", - "@rollup/plugin-node-resolve": "^7.1.3", + "@rollup/plugin-commonjs": "^17.0.0", + "@rollup/plugin-node-resolve": "^11.0.1", "concurrently": "^3.6.1", "http-server": "^0.12.3", - "jsdoc": "^3.6.5", + "jsdoc": "^3.6.6", "markdownlint-cli": "^0.23.2", - "rollup": "^1.32.1", - "rollup-cli": "^1.0.9", + "rollup": "^2.36.1", "standard": "^14.3.4", "tui-jsdoc-template": "^1.2.2", "typescript": "^3.9.7", - "y-protocols": "^0.2.3" + "y-protocols": "^1.0.2" } } diff --git a/funding.cjs b/sponsor-y.js similarity index 100% rename from funding.cjs rename to sponsor-y.js diff --git a/src/index.js b/src/index.js index 53eb066d..628e61f4 100644 --- a/src/index.js +++ b/src/index.js @@ -32,8 +32,6 @@ export { createRelativePositionFromJSON, createAbsolutePositionFromRelativePosition, compareRelativePositions, - writeRelativePosition, - readRelativePosition, ID, createID, compareIDs, @@ -65,6 +63,7 @@ export { decodeStateVector, logUpdate, logUpdateV2, + relativePositionToJSON, isDeleted, isParentOf, equalSnapshots, diff --git a/src/types/YText.js b/src/types/YText.js index 8457ea4d..638846fa 100644 --- a/src/types/YText.js +++ b/src/types/YText.js @@ -169,6 +169,8 @@ const insertNegatedAttributes = (transaction, parent, currPos, negatedAttributes negatedAttributes.forEach((val, key) => { left = new Item(createID(ownClientId, getState(doc.store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val)) left.integrate(transaction, 0) + currPos.currentAttributes.set(key, val) + updateCurrentAttributes(currPos.currentAttributes, /** @type {ContentFormat} */ (left.content)) }) } diff --git a/src/utils/Doc.js b/src/utils/Doc.js index bec8966b..b18ea09e 100644 --- a/src/utils/Doc.js +++ b/src/utils/Doc.js @@ -217,6 +217,9 @@ export class Doc extends Observable { /** * Converts the entire document into a js object, recursively traversing each yjs type + * Doesn't log types that have not been defined (using ydoc.getType(..)). + * + * @deprecated Do not use this method and rather call toJSON directly on the shared types. * * @return {Object} */ diff --git a/src/utils/RelativePosition.js b/src/utils/RelativePosition.js index dc435359..764eedb5 100644 --- a/src/utils/RelativePosition.js +++ b/src/utils/RelativePosition.js @@ -45,8 +45,9 @@ export class RelativePosition { * @param {ID|null} type * @param {string|null} tname * @param {ID|null} item + * @param {number} assoc */ - constructor (type, tname, item) { + constructor (type, tname, item, assoc = 0) { /** * @type {ID|null} */ @@ -59,23 +60,57 @@ export class RelativePosition { * @type {ID | null} */ this.item = item + /** + * A relative position is associated to a specific character. By default + * assoc >= 0, the relative position is associated to the character + * after the meant position. + * I.e. position 1 in 'ab' is associated to character 'b'. + * + * If assoc < 0, then the relative position is associated to the caharacter + * before the meant position. + * + * @type {number} + */ + this.assoc = assoc } } +/** + * @param {RelativePosition} rpos + * @return {any} + */ +export const relativePositionToJSON = rpos => { + const json = {} + if (rpos.type) { + json.type = rpos.type + } + if (rpos.tname) { + json.tname = rpos.tname + } + if (rpos.item) { + json.item = rpos.item + } + if (rpos.assoc != null) { + json.assoc = rpos.assoc + } + return json +} + /** * @param {any} json * @return {RelativePosition} * * @function */ -export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock)) +export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc) export class AbsolutePosition { /** * @param {AbstractType} type * @param {number} index + * @param {number} [assoc] */ - constructor (type, index) { + constructor (type, index, assoc = 0) { /** * @type {AbstractType} */ @@ -84,24 +119,27 @@ export class AbsolutePosition { * @type {number} */ this.index = index + this.assoc = assoc } } /** * @param {AbstractType} type * @param {number} index + * @param {number} [assoc] * * @function */ -export const createAbsolutePosition = (type, index) => new AbsolutePosition(type, index) +export const createAbsolutePosition = (type, index, assoc = 0) => new AbsolutePosition(type, index, assoc) /** * @param {AbstractType} type * @param {ID|null} item + * @param {number} [assoc] * * @function */ -export const createRelativePosition = (type, item) => { +export const createRelativePosition = (type, item, assoc) => { let typeid = null let tname = null if (type._item === null) { @@ -109,7 +147,7 @@ export const createRelativePosition = (type, item) => { } else { typeid = createID(type._item.id.client, type._item.id.clock) } - return new RelativePosition(typeid, tname, item) + return new RelativePosition(typeid, tname, item, assoc) } /** @@ -117,23 +155,35 @@ export const createRelativePosition = (type, item) => { * * @param {AbstractType} type The base type (e.g. YText or YArray). * @param {number} index The absolute position. + * @param {number} [assoc] * @return {RelativePosition} * * @function */ -export const createRelativePositionFromTypeIndex = (type, index) => { +export const createRelativePositionFromTypeIndex = (type, index, assoc = 0) => { let t = type._start + if (assoc < 0) { + // associated to the left character or the beginning of a type, increment index if possible. + if (index === 0) { + return createRelativePosition(type, null, assoc) + } + index-- + } while (t !== null) { if (!t.deleted && t.countable) { if (t.length > index) { // case 1: found position somewhere in the linked list - return createRelativePosition(type, createID(t.id.client, t.id.clock + index)) + return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc) } index -= t.length } + if (t.right === null && assoc < 0) { + // left-associated position, return last available id + return createRelativePosition(type, t.lastId, assoc) + } t = t.right } - return createRelativePosition(type, null) + return createRelativePosition(type, null, assoc) } /** @@ -143,7 +193,7 @@ export const createRelativePositionFromTypeIndex = (type, index) => { * @function */ export const writeRelativePosition = (encoder, rpos) => { - const { type, tname, item } = rpos + const { type, tname, item, assoc } = rpos if (item !== null) { encoding.writeVarUint(encoder, 0) writeID(encoder, item) @@ -158,6 +208,7 @@ export const writeRelativePosition = (encoder, rpos) => { } else { throw error.unexpectedCase() } + encoding.writeVarInt(encoder, assoc) return encoder } @@ -173,7 +224,7 @@ export const encodeRelativePosition = rpos => { /** * @param {decoding.Decoder} decoder - * @return {RelativePosition|null} + * @return {RelativePosition} * * @function */ @@ -195,12 +246,13 @@ export const readRelativePosition = decoder => { type = readID(decoder) } } - return new RelativePosition(type, tname, itemID) + const assoc = decoding.hasContent(decoder) ? decoding.readVarInt(decoder) : 0 + return new RelativePosition(type, tname, itemID, assoc) } /** * @param {Uint8Array} uint8Array - * @return {RelativePosition|null} + * @return {RelativePosition} */ export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array)) @@ -216,6 +268,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { const rightID = rpos.item const typeID = rpos.type const tname = rpos.tname + const assoc = rpos.assoc let type = null let index = 0 if (rightID !== null) { @@ -229,7 +282,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { } type = /** @type {AbstractType} */ (right.parent) if (type._item === null || !type._item.deleted) { - index = right.deleted || !right.countable ? 0 : res.diff + index = (right.deleted || !right.countable) ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary let n = right.left while (n !== null) { if (!n.deleted && n.countable) { @@ -256,9 +309,13 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { } else { throw error.unexpectedCase() } - index = type._length + if (assoc >= 0) { + index = type._length + } else { + index = 0 + } } - return createAbsolutePosition(type, index) + return createAbsolutePosition(type, index, rpos.assoc) } /** @@ -269,5 +326,5 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { * @function */ export const compareRelativePositions = (a, b) => a === b || ( - a !== null && b !== null && a.tname === b.tname && compareIDs(a.item, b.item) && compareIDs(a.type, b.type) + a !== null && b !== null && a.tname === b.tname && compareIDs(a.item, b.item) && compareIDs(a.type, b.type) && a.assoc === b.assoc ) diff --git a/src/utils/Transaction.js b/src/utils/Transaction.js index 1273d26e..1733b675 100644 --- a/src/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -340,14 +340,14 @@ const cleanupTransactions = (transactionCleanups, i) => { const encoder = new UpdateEncoderV1() const hasContent = writeUpdateMessageFromTransaction(encoder, transaction) if (hasContent) { - doc.emit('update', [encoder.toUint8Array(), transaction.origin, doc]) + doc.emit('update', [encoder.toUint8Array(), transaction.origin, doc, transaction]) } } if (doc._observers.has('updateV2')) { const encoder = new UpdateEncoderV2() const hasContent = writeUpdateMessageFromTransaction(encoder, transaction) if (hasContent) { - doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc]) + doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc, transaction]) } } transaction.subdocsAdded.forEach(subdoc => doc.subdocs.add(subdoc)) diff --git a/tests/index.js b/tests/index.js index f8b453ed..53a85b90 100644 --- a/tests/index.js +++ b/tests/index.js @@ -9,6 +9,7 @@ import * as compatibility from './compatibility.tests.js' import * as doc from './doc.tests.js' import * as snapshot from './snapshot.tests.js' import * as updates from './updates.tests.js' +import * as relativePositions from './relativePositions.tests.js' import { runTests } from 'lib0/testing.js' import { isBrowser, isNode } from 'lib0/environment.js' @@ -18,7 +19,7 @@ if (isBrowser) { log.createVConsole(document.body) } runTests({ - doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates + doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions }).then(success => { /* istanbul ignore next */ if (isNode) { diff --git a/tests/relativePositions.tests.js b/tests/relativePositions.tests.js new file mode 100644 index 00000000..15b63c47 --- /dev/null +++ b/tests/relativePositions.tests.js @@ -0,0 +1,104 @@ + +import * as Y from '../src/internals' +import * as t from 'lib0/testing.js' + +/** + * @param {Y.YText} ytext + */ +const checkRelativePositions = ytext => { + // test if all positions are encoded and restored correctly + for (let i = 0; i < ytext.length; i++) { + // for all types of associations.. + for (let assoc = -1; assoc < 2; assoc++) { + const rpos = Y.createRelativePositionFromTypeIndex(ytext, i, assoc) + const encodedRpos = Y.encodeRelativePosition(rpos) + const decodedRpos = Y.decodeRelativePosition(encodedRpos) + const absPos = /** @type {Y.AbsolutePosition} */ (Y.createAbsolutePositionFromRelativePosition(decodedRpos, /** @type {Y.Doc} */ (ytext.doc))) + t.assert(absPos.index === i) + t.assert(absPos.assoc === assoc) + } + } +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase1 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, '1') + ytext.insert(0, 'abc') + ytext.insert(0, 'z') + ytext.insert(0, 'y') + ytext.insert(0, 'x') + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase2 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'abc') + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase3 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, 'abc') + ytext.insert(0, '1') + ytext.insert(0, 'xyz') + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase4 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, '1') + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase5 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, '2') + ytext.insert(0, '1') + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionCase6 = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + checkRelativePositions(ytext) +} + +/** + * @param {t.TestCase} tc + */ +export const testRelativePositionAssociationDifference = tc => { + const ydoc = new Y.Doc() + const ytext = ydoc.getText() + ytext.insert(0, '2') + ytext.insert(0, '1') + const rposRight = Y.createRelativePositionFromTypeIndex(ytext, 1, 0) + const rposLeft = Y.createRelativePositionFromTypeIndex(ytext, 1, -1) + ytext.insert(1, 'x') + const posRight = Y.createAbsolutePositionFromRelativePosition(rposRight, ydoc) + const posLeft = Y.createAbsolutePositionFromRelativePosition(rposLeft, ydoc) + t.assert(posRight != null && posRight.index === 2) + t.assert(posLeft != null && posLeft.index === 1) +} diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index 62964520..3bd88365 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -78,6 +78,29 @@ export const testBasicFormat = tc => { compare(users) } +/** + * @param {t.TestCase} tc + */ +export const testMultilineFormat = tc => { + const ydoc = new Y.Doc() + const testText = ydoc.getText('test') + testText.insert(0, 'Test\nMulti-line\nFormatting') + testText.applyDelta([ + { retain: 4, attributes: { bold: true } }, + { retain: 1 }, // newline character + { retain: 10, attributes: { bold: true } }, + { retain: 1 }, // newline character + { retain: 10, attributes: { bold: true } } + ]) + t.compare(testText.toDelta(), [ + { insert: 'Test', attributes: { bold: true } }, + { insert: '\n' }, + { insert: 'Multi-line', attributes: { bold: true } }, + { insert: '\n' }, + { insert: 'Formatting', attributes: { bold: true } } + ]) +} + /** * @param {t.TestCase} tc */ @@ -286,7 +309,9 @@ export const testBestCase = tc => { } const tryGc = () => { + // @ts-ignore if (typeof global !== 'undefined' && global.gc) { + // @ts-ignore global.gc() } }