use reporter

This commit is contained in:
John McLear 2021-01-26 11:09:22 +00:00
commit 697bfd979b
60 changed files with 1520 additions and 1395 deletions

644
package-lock.json generated
View file

@ -183,6 +183,15 @@
"event-target-shim": "^5.0.0"
}
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@ -223,6 +232,11 @@
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz",
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg=="
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"agentkeepalive": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
@ -288,6 +302,11 @@
"sprintf-js": "~1.0.2"
}
},
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -327,11 +346,26 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@ -354,6 +388,11 @@
"safe-buffer": "^5.1.1"
}
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@ -518,6 +557,21 @@
"delayed-stream": "~1.0.0"
}
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -529,6 +583,11 @@
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"optional": true
},
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -689,6 +748,74 @@
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"engine.io": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
"integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"debug": "~4.1.0",
"engine.io-parser": "~2.2.0",
"ws": "~7.4.2"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
}
}
},
"engine.io-client": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
"integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
"requires": {
"component-emitter": "~1.3.0",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.2.0",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"engine.io-parser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.4",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
}
},
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
@ -736,7 +863,7 @@
"security": "1.0.0",
"semver": "5.6.0",
"slide": "1.1.6",
"socket.io": "^2.3.0",
"socket.io": "^2.4.1",
"terser": "^4.7.0",
"threads": "^1.4.0",
"tiny-worker": "^2.3.0",
@ -1220,11 +1347,6 @@
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng=="
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"aggregate-error": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
@ -1371,11 +1493,6 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -1399,11 +1516,6 @@
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"async-stacktrace": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/async-stacktrace/-/async-stacktrace-0.0.2.tgz",
@ -1424,31 +1536,16 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"bail": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
"integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"bath-es5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bath-es5/-/bath-es5-3.0.3.tgz",
@ -1462,14 +1559,6 @@
"tweetnacl": "^0.14.3"
}
},
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
"requires": {
"callsite": "1.0.0"
}
},
"binary-search": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
@ -1485,11 +1574,6 @@
"readable-stream": "^3.4.0"
}
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@ -1571,11 +1655,6 @@
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
},
"callsite": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1665,21 +1744,6 @@
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
},
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"compress-commons": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz",
@ -1759,11 +1823,6 @@
}
}
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
@ -1963,92 +2022,6 @@
"once": "^1.4.0"
}
},
"engine.io": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz",
"integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "0.3.1",
"debug": "~4.1.0",
"engine.io-parser": "~2.2.0",
"ws": "^7.1.2"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"engine.io-client": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz",
"integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==",
"requires": {
"component-emitter": "~1.3.0",
"component-inherit": "0.0.3",
"debug": "~4.1.0",
"engine.io-parser": "~2.2.0",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"ws": "~6.1.0",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"ws": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"engine.io-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
"integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.5",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
}
},
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
@ -2658,26 +2631,6 @@
"har-schema": "^2.0.0"
}
},
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"requires": {
"isarray": "2.0.1"
},
"dependencies": {
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"hasha": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz",
@ -2853,11 +2806,6 @@
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
},
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -7186,11 +7134,6 @@
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-component": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
},
"observable-fns": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.5.1.tgz",
@ -7297,22 +7240,6 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="
},
"parseqs": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseuri": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -7769,135 +7696,6 @@
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
},
"socket.io": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
"integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
"requires": {
"debug": "~4.1.0",
"engine.io": "~3.4.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.3.0",
"socket.io-parser": "~3.4.0"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"socket.io-adapter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
},
"socket.io-client": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
"requires": {
"backo2": "1.0.2",
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0",
"component-emitter": "1.2.1",
"debug": "~4.1.0",
"engine.io-client": "~3.4.0",
"has-binary2": "~1.0.2",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"object-component": "0.0.3",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"socket.io-parser": "~3.3.0",
"to-array": "0.1.4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"socket.io-parser": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
"requires": {
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"isarray": "2.0.1"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
}
}
},
"socket.io-parser": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
"integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
"requires": {
"component-emitter": "1.2.1",
"debug": "~4.1.0",
"isarray": "2.0.1"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -8284,11 +8082,6 @@
"resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.0.1.tgz",
"integrity": "sha1-beEM1SGaHxIdmgokssEbP7JN/+0="
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -8578,26 +8371,11 @@
"typedarray-to-buffer": "^3.1.5"
}
},
"ws": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w=="
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
},
"z-schema": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz",
@ -9126,6 +8904,26 @@
}
}
},
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"requires": {
"isarray": "2.0.1"
},
"dependencies": {
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -9194,6 +8992,11 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -9545,6 +9348,11 @@
}
}
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-addon-api": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
@ -9790,6 +9598,16 @@
"callsites": "^3.0.0"
}
},
"parseqs": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
},
"parseuri": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -10184,6 +10002,112 @@
"is-fullwidth-code-point": "^2.0.0"
}
},
"socket.io": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
"integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
"requires": {
"debug": "~4.1.0",
"engine.io": "~3.5.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.4.0",
"socket.io-parser": "~3.4.0"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
}
}
},
"socket.io-adapter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
},
"socket.io-client": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
"integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
"requires": {
"backo2": "1.0.2",
"component-bind": "1.0.0",
"component-emitter": "~1.3.0",
"debug": "~3.1.0",
"engine.io-client": "~3.5.0",
"has-binary2": "~1.0.2",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"socket.io-parser": "~3.3.0",
"to-array": "0.1.4"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"socket.io-parser": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
"integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
"requires": {
"component-emitter": "~1.3.0",
"debug": "~3.1.0",
"isarray": "2.0.1"
}
}
}
},
"socket.io-parser": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
"integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
"requires": {
"component-emitter": "1.2.1",
"debug": "~4.1.0",
"isarray": "2.0.1"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@ -10395,6 +10319,11 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
@ -10555,6 +10484,11 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
@ -10574,6 +10508,11 @@
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz",
"integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA=="
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
"xpath.js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz",
@ -10589,6 +10528,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
}
}

View file

@ -18,21 +18,39 @@
"Арсен Асхат"
]
},
"admin.page-title": "Панель администратора — Etherpad",
"admin_plugins": "Менеджер плагинов",
"admin_plugins.available": "Доступные плагины",
"admin_plugins.available_not-found": "Плагины не найдены.",
"admin_plugins.available_fetching": "Получение…",
"admin_plugins.available_install.value": "Установить",
"admin_plugins.available_search.placeholder": "Искать плагины для установки",
"admin_plugins.description": "Описание",
"admin_plugins.installed": "Установленные плагины",
"admin_plugins.installed_fetching": "Получение установленных плагинов…",
"admin_plugins.installed_nothing": "Вы еще не установили ни одного плагина.",
"admin_plugins.installed_uninstall.value": "Удалить",
"admin_plugins.last-update": "Последнее обновление",
"admin_plugins.name": "Название",
"admin_plugins.page-title": "Менеджер плагинов — Etherpad",
"admin_plugins.version": "Версия",
"admin_plugins_info": "Информация об устранении неполадок",
"admin_plugins_info.hooks": "Установленные крючки",
"admin_plugins_info.hooks_client": "Клиентские хуки",
"admin_plugins_info.hooks_server": "Серверные хуки",
"admin_plugins_info.parts": "Установленные части",
"admin_plugins_info.plugins": "Установленные плагины",
"admin_plugins_info.page-title": "Информация о плагине — Etherpad",
"admin_plugins_info.version": "Версия Etherpad",
"admin_plugins_info.version_latest": "Последняя доступная версия",
"admin_plugins_info.version_number": "Номер версии",
"admin_settings": "Настройки",
"admin_settings.current": "Текущая конфигурация",
"admin_settings.current_example-devel": "Пример шаблона настроек для среда разработки",
"admin_settings.current_example-prod": "Пример шаблона настроек для боевой среды",
"admin_settings.current_restart.value": "Перезагрузить Etherpad",
"admin_settings.current_save.value": "Сохранить настройки",
"admin_settings.page-title": "Настройки — Etherpad",
"index.newPad": "Создать",
"index.createOpenPad": "или создать/открыть документ с именем:",
"index.openPad": "откройте существующий документ с именем:",

View file

@ -1,3 +1,4 @@
'use strict';
/**
* This module provides all API functions
*/
@ -18,8 +19,8 @@
* limitations under the License.
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const customError = require('../utils/customError');
const Changeset = require('../../static/js/Changeset');
const CustomError = require('../utils/customError');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const readOnlyManager = require('./ReadOnlyManager');
@ -101,7 +102,7 @@ Example returns:
}
*/
exports.getAttributePool = async function (padID) {
exports.getAttributePool = async (padID) => {
const pad = await getPadSafe(padID, true);
return {pool: pad.pool};
};
@ -119,7 +120,7 @@ Example returns:
}
*/
exports.getRevisionChangeset = async function (padID, rev) {
exports.getRevisionChangeset = async (padID, rev) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@ -133,7 +134,7 @@ exports.getRevisionChangeset = async function (padID, rev) {
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
throw new customError('rev is higher than the head revision of the pad', 'apierror');
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
// get the changeset for this revision
@ -152,7 +153,7 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getText = async function (padID, rev) {
exports.getText = async (padID, rev) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@ -166,7 +167,7 @@ exports.getText = async function (padID, rev) {
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
throw new customError('rev is higher than the head revision of the pad', 'apierror');
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
// get the text of this revision
@ -188,10 +189,10 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
exports.setText = async function (padID, text) {
exports.setText = async (padID, text) => {
// text is required
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
throw new CustomError('text is not a string', 'apierror');
}
// get the pad
@ -212,10 +213,10 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
exports.appendText = async function (padID, text) {
exports.appendText = async (padID, text) => {
// text is required
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
throw new CustomError('text is not a string', 'apierror');
}
const pad = await getPadSafe(padID, true);
@ -233,7 +234,7 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getHTML = async function (padID, rev) {
exports.getHTML = async (padID, rev) => {
if (rev !== undefined) {
rev = checkValidRev(rev);
}
@ -245,7 +246,7 @@ exports.getHTML = async function (padID, rev) {
// check if this is a valid revision
const head = pad.getHeadRevisionNumber();
if (rev > head) {
throw new customError('rev is higher than the head revision of the pad', 'apierror');
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
}
@ -265,10 +266,10 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setHTML = async function (padID, html) {
exports.setHTML = async (padID, html) => {
// html string is required
if (typeof html !== 'string') {
throw new customError('html is not a string', 'apierror');
throw new CustomError('html is not a string', 'apierror');
}
// get the pad
@ -278,7 +279,7 @@ exports.setHTML = async function (padID, html) {
try {
await importHtml.setPadHTML(pad, cleanText(html));
} catch (e) {
throw new customError('HTML is malformed', 'apierror');
throw new CustomError('HTML is malformed', 'apierror');
}
// update the clients on the pad
@ -294,23 +295,25 @@ getChatHistory(padId, start, end), returns a part of or the whole chat-history o
Example returns:
{"code":0,"message":"ok","data":{"messages":[{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
{"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}]}}
{"code":0,"message":"ok","data":{"messages":[
{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
{"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}
]}}
{code: 1, message:"start is higher or equal to the current chatHead", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getChatHistory = async function (padID, start, end) {
exports.getChatHistory = async (padID, start, end) => {
if (start && end) {
if (start < 0) {
throw new customError('start is below zero', 'apierror');
throw new CustomError('start is below zero', 'apierror');
}
if (end < 0) {
throw new customError('end is below zero', 'apierror');
throw new CustomError('end is below zero', 'apierror');
}
if (start > end) {
throw new customError('start is higher than end', 'apierror');
throw new CustomError('start is higher than end', 'apierror');
}
}
@ -320,16 +323,16 @@ exports.getChatHistory = async function (padID, start, end) {
const chatHead = pad.chatHead;
// fall back to getting the whole chat-history if a parameter is missing
if (!start || !end) {
if (!start || !end) {
start = 0;
end = pad.chatHead;
}
if (start > chatHead) {
throw new customError('start is higher than the current chatHead', 'apierror');
throw new CustomError('start is higher than the current chatHead', 'apierror');
}
if (end > chatHead) {
throw new customError('end is higher than the current chatHead', 'apierror');
throw new CustomError('end is higher than the current chatHead', 'apierror');
}
// the the whole message-log and return it to the client
@ -339,21 +342,22 @@ exports.getChatHistory = async function (padID, start, end) {
};
/**
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id,
time is a timestamp
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.appendChatMessage = async function (padID, text, authorID, time) {
exports.appendChatMessage = async (padID, text, authorID, time) => {
// text is required
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
throw new CustomError('text is not a string', 'apierror');
}
// if time is not an integer value set time to current timestamp
if (time === undefined || !is_int(time)) {
if (time === undefined || !isInt(time)) {
time = Date.now();
}
@ -375,7 +379,7 @@ Example returns:
{code: 0, message:"ok", data: {revisions: 56}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getRevisionsCount = async function (padID) {
exports.getRevisionsCount = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {revisions: pad.getHeadRevisionNumber()};
@ -389,7 +393,7 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getSavedRevisionsCount = async function (padID) {
exports.getSavedRevisionsCount = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsNumber()};
@ -403,7 +407,7 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.listSavedRevisions = async function (padID) {
exports.listSavedRevisions = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsList()};
@ -417,7 +421,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.saveRevision = async function (padID, rev) {
exports.saveRevision = async (padID, rev) => {
// check if rev is a number
if (rev !== undefined) {
rev = checkValidRev(rev);
@ -430,7 +434,7 @@ exports.saveRevision = async function (padID, rev) {
// the client asked for a special revision
if (rev !== undefined) {
if (rev > head) {
throw new customError('rev is higher than the head revision of the pad', 'apierror');
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
} else {
rev = pad.getHeadRevisionNumber();
@ -448,7 +452,7 @@ Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getLastEdited = async function (padID) {
exports.getLastEdited = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
const lastEdited = await pad.getLastEdit();
@ -463,16 +467,16 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null}
*/
exports.createPad = async function (padID, text) {
exports.createPad = async (padID, text) => {
if (padID) {
// ensure there is no $ in the padID
if (padID.indexOf('$') !== -1) {
throw new customError("createPad can't create group pads", 'apierror');
throw new CustomError("createPad can't create group pads", 'apierror');
}
// check for url special characters
if (padID.match(/(\/|\?|&|#)/)) {
throw new customError('malformed padID: Remove special characters', 'apierror');
throw new CustomError('malformed padID: Remove special characters', 'apierror');
}
}
@ -488,7 +492,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.deletePad = async function (padID) {
exports.deletePad = async (padID) => {
const pad = await getPadSafe(padID, true);
await pad.remove();
};
@ -501,10 +505,10 @@ exports.deletePad = async function (padID) {
{code:0, message:"ok", data:null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.restoreRevision = async function (padID, rev) {
exports.restoreRevision = async (padID, rev) => {
// check if rev is a number
if (rev === undefined) {
throw new customError('rev is not defined', 'apierror');
throw new CustomError('rev is not defined', 'apierror');
}
rev = checkValidRev(rev);
@ -513,7 +517,7 @@ exports.restoreRevision = async function (padID, rev) {
// check if this is a valid revision
if (rev > pad.getHeadRevisionNumber()) {
throw new customError('rev is higher than the head revision of the pad', 'apierror');
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
const atext = await pad.getInternalRevisionAText(rev);
@ -521,7 +525,7 @@ exports.restoreRevision = async function (padID, rev) {
const oldText = pad.text();
atext.text += '\n';
function eachAttribRun(attribs, func) {
const eachAttribRun = (attribs, func) => {
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = 0;
@ -534,7 +538,7 @@ exports.restoreRevision = async function (padID, rev) {
}
textIndex = nextIndex;
}
}
};
// create a new changeset with a helper builder object
const builder = Changeset.builder(oldText.length);
@ -569,7 +573,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.copyPad = async function (sourceID, destinationID, force) {
exports.copyPad = async (sourceID, destinationID, force) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
};
@ -583,7 +587,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) {
exports.copyPadWithoutHistory = async (sourceID, destinationID, force) => {
const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force);
};
@ -597,7 +601,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.movePad = async function (sourceID, destinationID, force) {
exports.movePad = async (sourceID, destinationID, force) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
await pad.remove();
@ -611,7 +615,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getReadOnlyID = async function (padID) {
exports.getReadOnlyID = async (padID) => {
// we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true);
@ -629,11 +633,11 @@ Example returns:
{code: 0, message:"ok", data: {padID: padID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getPadID = async function (roID) {
exports.getPadID = async (roID) => {
// get the PadId
const padID = await readOnlyManager.getPadId(roID);
if (padID === null) {
throw new customError('padID does not exist', 'apierror');
if (padID == null) {
throw new CustomError('padID does not exist', 'apierror');
}
return {padID};
@ -647,7 +651,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setPublicStatus = async function (padID, publicStatus) {
exports.setPublicStatus = async (padID, publicStatus) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@ -670,7 +674,7 @@ Example returns:
{code: 0, message:"ok", data: {publicStatus: true}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getPublicStatus = async function (padID) {
exports.getPublicStatus = async (padID) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@ -687,7 +691,7 @@ Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null}
*/
exports.listAuthorsOfPad = async function (padID) {
exports.listAuthorsOfPad = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
const authorIDs = pad.getAllAuthors();
@ -717,7 +721,7 @@ Example returns:
{code: 1, message:"padID does not exist"}
*/
exports.sendClientsMessage = async function (padID, msg) {
exports.sendClientsMessage = async (padID, msg) => {
const pad = await getPadSafe(padID, true);
padMessageHandler.handleCustomMessage(padID, msg);
};
@ -730,7 +734,7 @@ Example returns:
{"code":0,"message":"ok","data":null}
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.checkToken = async function () {
exports.checkToken = async () => {
};
/**
@ -741,7 +745,7 @@ Example returns:
{code: 0, message:"ok", data: {chatHead: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getChatHead = async function (padID) {
exports.getChatHead = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {chatHead: pad.chatHead};
@ -751,11 +755,21 @@ exports.getChatHead = async function (padID) {
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
Example returns:
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{
"code": 0,
"message": "ok",
"data": {
"html": "...",
"authors": [
"a.HKIv23mEbachFYfH",
""
]
}
}
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.createDiffHTML = async function (padID, startRev, endRev) {
exports.createDiffHTML = async (padID, startRev, endRev) => {
// check if startRev is a number
if (startRev !== undefined) {
startRev = checkValidRev(startRev);
@ -768,8 +782,9 @@ exports.createDiffHTML = async function (padID, startRev, endRev) {
// get the pad
const pad = await getPadSafe(padID, true);
let padDiff;
try {
var padDiff = new PadDiff(pad, startRev, endRev);
padDiff = new PadDiff(pad, startRev, endRev);
} catch (e) {
throw {stop: e.message};
}
@ -793,7 +808,7 @@ exports.createDiffHTML = async function (padID, startRev, endRev) {
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.getStats = async function () {
exports.getStats = async () => {
const sessionInfos = padMessageHandler.sessioninfos;
const sessionKeys = Object.keys(sessionInfos);
@ -813,20 +828,18 @@ exports.getStats = async function () {
**************************** */
// checks if a number is an int
function is_int(value) {
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
}
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
// gets a pad safe
async function getPadSafe(padID, shouldExist, text) {
// check if padID is a string
if (typeof padID !== 'string') {
throw new customError('padID is not a string', 'apierror');
throw new CustomError('padID is not a string', 'apierror');
}
// check if the padID maches the requirements
if (!padManager.isValidPadId(padID)) {
throw new customError('padID did not match requirements', 'apierror');
throw new CustomError('padID did not match requirements', 'apierror');
}
// check if the pad exists
@ -834,12 +847,12 @@ async function getPadSafe(padID, shouldExist, text) {
if (!exists && shouldExist) {
// does not exist, but should
throw new customError('padID does not exist', 'apierror');
throw new CustomError('padID does not exist', 'apierror');
}
if (exists && !shouldExist) {
// does exist, but shouldn't
throw new customError('padID does already exist', 'apierror');
throw new CustomError('padID does already exist', 'apierror');
}
// pad exists, let's get it
@ -848,33 +861,34 @@ async function getPadSafe(padID, shouldExist, text) {
// checks if a rev is a legal number
// pre-condition is that `rev` is not undefined
function checkValidRev(rev) {
const checkValidRev = (rev) => {
if (typeof rev !== 'number') {
rev = parseInt(rev, 10);
}
// check if rev is a number
if (isNaN(rev)) {
throw new customError('rev is not a number', 'apierror');
throw new CustomError('rev is not a number', 'apierror');
}
// ensure this is not a negative number
if (rev < 0) {
throw new customError('rev is not a negative number', 'apierror');
throw new CustomError('rev is not a negative number', 'apierror');
}
// ensure this is not a float value
if (!is_int(rev)) {
throw new customError('rev is a float value', 'apierror');
if (!isInt(rev)) {
throw new CustomError('rev is a float value', 'apierror');
}
return rev;
}
};
// checks if a padID is part of a group
function checkGroupPad(padID, field) {
const checkGroupPad = (padID, field) => {
// ensure this is a group pad
if (padID && padID.indexOf('$') === -1) {
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
}
throw new CustomError(
`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
}
};

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The AuthorManager controlls all information about the Pad authors
*/
@ -19,11 +20,10 @@
*/
const db = require('./DB');
const customError = require('../utils/customError');
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
const CustomError = require('../utils/customError');
const randomString = require('../../static/js/pad_utils').randomString;
exports.getColorPalette = function () {
return [
exports.getColorPalette = () => [
'#ffc7c7',
'#fff1c7',
'#e3ffc7',
@ -89,15 +89,14 @@ exports.getColorPalette = function () {
'#f8d2a0',
'#b3b3e6',
];
};
/**
* Checks if the author exists
*/
exports.doesAuthorExist = async function (authorID) {
exports.doesAuthorExist = async (authorID) => {
const author = await db.get(`globalAuthor:${authorID}`);
return author !== null;
return author != null;
};
/* exported for backwards compatibility */
@ -107,7 +106,7 @@ exports.doesAuthorExists = exports.doesAuthorExist;
* Returns the AuthorID for a token.
* @param {String} token The token
*/
exports.getAuthor4Token = async function (token) {
exports.getAuthor4Token = async (token) => {
const author = await mapAuthorWithDBKey('token2author', token);
// return only the sub value authorID
@ -119,7 +118,7 @@ exports.getAuthor4Token = async function (token) {
* @param {String} token The mapper
* @param {String} name The name of the author (optional)
*/
exports.createAuthorIfNotExistsFor = async function (authorMapper, name) {
exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
if (name) {
@ -140,7 +139,7 @@ async function mapAuthorWithDBKey(mapperkey, mapper) {
// try to map to an author
const author = await db.get(`${mapperkey}:${mapper}`);
if (author === null) {
if (author == null) {
// there is no author with this mapper, so create one
const author = await exports.createAuthor(null);
@ -163,7 +162,7 @@ async function mapAuthorWithDBKey(mapperkey, mapper) {
* Internal function that creates the database entry for an author
* @param {String} name The name of the author
*/
exports.createAuthor = function (name) {
exports.createAuthor = (name) => {
// create the new author name
const author = `a.${randomString(16)}`;
@ -185,50 +184,40 @@ exports.createAuthor = function (name) {
* Returns the Author Obj of the author
* @param {String} author The id of the author
*/
exports.getAuthor = function (author) {
// NB: result is already a Promise
return db.get(`globalAuthor:${author}`);
};
exports.getAuthor = (author) => db.get(`globalAuthor:${author}`);
/**
* Returns the color Id of the author
* @param {String} author The id of the author
*/
exports.getAuthorColorId = function (author) {
return db.getSub(`globalAuthor:${author}`, ['colorId']);
};
exports.getAuthorColorId = (author) => db.getSub(`globalAuthor:${author}`, ['colorId']);
/**
* Sets the color Id of the author
* @param {String} author The id of the author
* @param {String} colorId The color id of the author
*/
exports.setAuthorColorId = function (author, colorId) {
return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId);
};
exports.setAuthorColorId = (author, colorId) => db.setSub(
`globalAuthor:${author}`, ['colorId'], colorId);
/**
* Returns the name of the author
* @param {String} author The id of the author
*/
exports.getAuthorName = function (author) {
return db.getSub(`globalAuthor:${author}`, ['name']);
};
exports.getAuthorName = (author) => db.getSub(`globalAuthor:${author}`, ['name']);
/**
* Sets the name of the author
* @param {String} author The id of the author
* @param {String} name The name of the author
*/
exports.setAuthorName = function (author, name) {
return db.setSub(`globalAuthor:${author}`, ['name'], name);
};
exports.setAuthorName = (author, name) => db.setSub(`globalAuthor:${author}`, ['name'], name);
/**
* Returns an array of all pads this author contributed to
* @param {String} author The id of the author
*/
exports.listPadsOfAuthor = async function (authorID) {
exports.listPadsOfAuthor = async (authorID) => {
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
@ -237,9 +226,9 @@ exports.listPadsOfAuthor = async function (authorID) {
// get the globalAuthor
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) {
if (author == null) {
// author does not exist
throw new customError('authorID does not exist', 'apierror');
throw new CustomError('authorID does not exist', 'apierror');
}
// everything is fine, return the pad IDs
@ -253,11 +242,11 @@ exports.listPadsOfAuthor = async function (authorID) {
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.addPad = async function (authorID, padID) {
exports.addPad = async (authorID, padID) => {
// get the entry
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return;
if (author == null) return;
/*
* ACHTUNG: padIDs can also be undefined, not just null, so it is not possible
@ -280,12 +269,12 @@ exports.addPad = async function (authorID, padID) {
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.removePad = async function (authorID, padID) {
exports.removePad = async (authorID, padID) => {
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return;
if (author == null) return;
if (author.padIDs !== null) {
if (author.padIDs != null) {
// remove pad from author
delete author.padIDs[padID];
await db.set(`globalAuthor:${authorID}`, author);

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The Group Manager provides functions to manage groups in the database
*/
@ -18,13 +19,13 @@
* limitations under the License.
*/
const customError = require('../utils/customError');
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
const CustomError = require('../utils/customError');
const randomString = require('../../static/js/pad_utils').randomString;
const db = require('./DB');
const padManager = require('./PadManager');
const sessionManager = require('./SessionManager');
exports.listAllGroups = async function () {
exports.listAllGroups = async () => {
let groups = await db.get('groups');
groups = groups || {};
@ -32,17 +33,20 @@ exports.listAllGroups = async function () {
return {groupIDs};
};
exports.deleteGroup = async function (groupID) {
exports.deleteGroup = async (groupID) => {
const group = await db.get(`group:${groupID}`);
// ensure group exists
if (group == null) {
// group does not exist
throw new customError('groupID does not exist', 'apierror');
throw new CustomError('groupID does not exist', 'apierror');
}
// iterate through all pads of this group and delete them (in parallel)
await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove())));
await Promise.all(Object.keys(group.pads)
.map((padID) => padManager.getPad(padID)
.then((pad) => pad.remove())
));
// iterate through group2sessions and delete all sessions
const group2sessions = await db.get(`group2sessions:${groupID}`);
@ -76,14 +80,14 @@ exports.deleteGroup = async function (groupID) {
await db.set('groups', newGroups);
};
exports.doesGroupExist = async function (groupID) {
exports.doesGroupExist = async (groupID) => {
// try to get the group entry
const group = await db.get(`group:${groupID}`);
return (group != null);
};
exports.createGroup = async function () {
exports.createGroup = async () => {
// search for non existing groupID
const groupID = `g.${randomString(16)}`;
@ -103,10 +107,10 @@ exports.createGroup = async function () {
return {groupID};
};
exports.createGroupIfNotExistsFor = async function (groupMapper) {
exports.createGroupIfNotExistsFor = async (groupMapper) => {
// ensure mapper is optional
if (typeof groupMapper !== 'string') {
throw new customError('groupMapper is not a string', 'apierror');
throw new CustomError('groupMapper is not a string', 'apierror');
}
// try to get a group for this mapper
@ -128,7 +132,7 @@ exports.createGroupIfNotExistsFor = async function (groupMapper) {
return result;
};
exports.createGroupPad = async function (groupID, padName, text) {
exports.createGroupPad = async (groupID, padName, text) => {
// create the padID
const padID = `${groupID}$${padName}`;
@ -136,7 +140,7 @@ exports.createGroupPad = async function (groupID, padName, text) {
const groupExists = await exports.doesGroupExist(groupID);
if (!groupExists) {
throw new customError('groupID does not exist', 'apierror');
throw new CustomError('groupID does not exist', 'apierror');
}
// ensure pad doesn't exist already
@ -144,7 +148,7 @@ exports.createGroupPad = async function (groupID, padName, text) {
if (padExists) {
// pad exists already
throw new customError('padName does already exist', 'apierror');
throw new CustomError('padName does already exist', 'apierror');
}
// create the pad
@ -156,12 +160,12 @@ exports.createGroupPad = async function (groupID, padName, text) {
return {padID};
};
exports.listPads = async function (groupID) {
exports.listPads = async (groupID) => {
const exists = await exports.doesGroupExist(groupID);
// ensure the group exists
if (!exists) {
throw new customError('groupID does not exist', 'apierror');
throw new CustomError('groupID does not exist', 'apierror');
}
// group exists, let's get the pads

View file

@ -1,21 +1,21 @@
'use strict';
/**
* The pad object, defined with joose
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
const Changeset = require('../../static/js/Changeset');
const AttributePool = require('../../static/js/AttributePool');
const db = require('./DB');
const settings = require('../utils/Settings');
const authorManager = require('./AuthorManager');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const groupManager = require('./GroupManager');
const customError = require('../utils/customError');
const CustomError = require('../utils/customError');
const readOnlyManager = require('./ReadOnlyManager');
const crypto = require('crypto');
const randomString = require('../utils/randomstring');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const hooks = require('../../static/js/pluginfw/hooks');
const promises = require('../utils/promises');
// serialization/deserialization attributes
@ -23,13 +23,14 @@ const attributeBlackList = ['id'];
const jsonableList = ['pool'];
/**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
* line breaks and convert Tabs to spaces
* @param txt
*/
exports.cleanText = function (txt) {
return txt.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
};
exports.cleanText = (txt) => txt.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\xa0/g, ' ');
const Pad = function Pad(id) {
this.atext = Changeset.makeAText('\n');
@ -56,7 +57,7 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
};
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
const savedRev = new Array();
const savedRev = [];
for (const rev in this.savedRevisions) {
savedRev.push(this.savedRevisions[rev].revNum);
}
@ -85,11 +86,11 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
newRevData.meta.timestamp = Date.now();
// ex. getNumForAuthor
if (author != '') {
if (author !== '') {
this.pool.putAttrib(['author', author || '']);
}
if (newRev % 100 == 0) {
if (newRev % 100 === 0) {
newRevData.meta.pool = this.pool;
newRevData.meta.atext = this.atext;
}
@ -104,7 +105,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
p.push(authorManager.addPad(author, this.id));
}
if (this.head == 0) {
if (this.head === 0) {
hooks.callAll('padCreate', {pad: this, author});
} else {
hooks.callAll('padUpdate', {pad: this, author, revs: newRev, changeset: aChangeset});
@ -153,7 +154,7 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
const authors = [];
for (const key in this.pool.numToAttrib) {
if (this.pool.numToAttrib[key][0] == 'author' && this.pool.numToAttrib[key][1] != '') {
if (this.pool.numToAttrib[key][0] === 'author' && this.pool.numToAttrib[key][1] !== '') {
authors.push(this.pool.numToAttrib[key][1]);
}
}
@ -177,7 +178,8 @@ Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText
// get all needed changesets
const changesets = [];
await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
await Promise.all(
neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
changesets[item] = changeset;
})));
@ -204,7 +206,8 @@ Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() {
const returnTable = {};
const colorPalette = authorManager.getColorPalette();
await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
await Promise.all(
authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
// colorId might be a hex color or an number out of the palette
returnTable[author] = colorPalette[colorId] || colorId;
})));
@ -227,7 +230,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
endRev = head;
}
if (startRev !== null && endRev !== null) {
if (startRev != null && endRev != null) {
return {startRev, endRev};
}
return null;
@ -251,7 +254,7 @@ Pad.prototype.setText = async function setText(newText) {
// We want to ensure the pad still ends with a \n, but otherwise keep
// getText() and setText() consistent.
let changeset;
if (newText[newText.length - 1] == '\n') {
if (newText[newText.length - 1] === '\n') {
changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText);
} else {
changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText);
@ -304,7 +307,8 @@ Pad.prototype.getChatMessages = async function getChatMessages(start, end) {
// get all entries out of the database
const entries = [];
await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
await Promise.all(
neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
entries[entryObject.order] = entry;
})));
@ -384,14 +388,16 @@ Pad.prototype.copy = async function copy(destinationID, force) {
// copy all chat messages
const chatHead = this.chatHead;
for (let i = 0; i <= chatHead; ++i) {
const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
const p = db.get(`pad:${sourceID}:chat:${i}`)
.then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
promises.push(p);
}
// copy all revisions
const revHead = this.head;
for (let i = 0; i <= revHead; ++i) {
const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
const p = db.get(`pad:${sourceID}:revs:${i}`)
.then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
promises.push(p);
}
@ -426,7 +432,7 @@ Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAnd
// group does not exist
if (!groupExists) {
throw new customError('groupID does not exist for destinationID', 'apierror');
throw new CustomError('groupID does not exist for destinationID', 'apierror');
}
}
return destGroupID;
@ -446,7 +452,7 @@ Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIf
if (exists) {
if (!force) {
console.error('erroring out without force');
throw new customError('destinationID already exists', 'apierror');
throw new CustomError('destinationID already exists', 'apierror');
}
// exists and forcing

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The Pad Manager is a Factory for pad Objects
*/
@ -18,7 +19,7 @@
* limitations under the License.
*/
const customError = require('../utils/customError');
const CustomError = require('../utils/customError');
const Pad = require('../db/Pad').Pad;
const db = require('./DB');
@ -109,22 +110,22 @@ const padList = {
* @param id A String with the id of the pad
* @param {Function} callback
*/
exports.getPad = async function (id, text) {
exports.getPad = async (id, text) => {
// check if this is a valid padId
if (!exports.isValidPadId(id)) {
throw new customError(`${id} is not a valid padId`, 'apierror');
throw new CustomError(`${id} is not a valid padId`, 'apierror');
}
// check if this is a valid text
if (text != null) {
// check if text is a string
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
throw new CustomError('text is not a string', 'apierror');
}
// check if text is less than 100k chars
if (text.length > 100000) {
throw new customError('text must be less than 100k chars', 'apierror');
throw new CustomError('text must be less than 100k chars', 'apierror');
}
}
@ -146,14 +147,14 @@ exports.getPad = async function (id, text) {
return pad;
};
exports.listAllPads = async function () {
exports.listAllPads = async () => {
const padIDs = await padList.getPads();
return {padIDs};
};
// checks if a pad exists
exports.doesPadExist = async function (padId) {
exports.doesPadExist = async (padId) => {
const value = await db.get(`pad:${padId}`);
return (value != null && value.atext);
@ -189,9 +190,7 @@ exports.sanitizePadId = async function sanitizePadId(padId) {
return padId;
};
exports.isValidPadId = function (padId) {
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
};
exports.isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
/**
* Removes the pad from database and unloads it.
@ -204,6 +203,6 @@ exports.removePad = async (padId) => {
};
// removes a pad from the cache
exports.unloadPad = function (padId) {
exports.unloadPad = (padId) => {
globalPads.remove(padId);
};

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The ReadOnlyManager manages the database and rendering releated to read only pads
*/
@ -27,15 +28,13 @@ const randomString = require('../utils/randomstring');
* checks if the id pattern matches a read-only pad id
* @param {String} the pad's id
*/
exports.isReadOnlyId = function (id) {
return id.indexOf('r.') === 0;
};
exports.isReadOnlyId = (id) => id.indexOf('r.') === 0;
/**
* returns a read only id for a pad
* @param {String} padId the id of the pad
*/
exports.getReadOnlyId = async function (padId) {
exports.getReadOnlyId = async (padId) => {
// check if there is a pad2readonly entry
let readOnlyId = await db.get(`pad2readonly:${padId}`);
@ -53,15 +52,13 @@ exports.getReadOnlyId = async function (padId) {
* returns the padId for a read only id
* @param {String} readOnlyId read only id
*/
exports.getPadId = function (readOnlyId) {
return db.get(`readonly2pad:${readOnlyId}`);
};
exports.getPadId = (readOnlyId) => db.get(`readonly2pad:${readOnlyId}`);
/**
* returns the padId and readonlyPadId in an object for any id
* @param {String} padIdOrReadonlyPadId read only id or real pad id
*/
exports.getIds = async function (id) {
exports.getIds = async (id) => {
const readonly = (id.indexOf('r.') === 0);
// Might be null, if this is an unknown read-only id

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Controls the security of pad access
*/
@ -19,7 +20,7 @@
*/
const authorManager = require('./AuthorManager');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const hooks = require('../../static/js/pluginfw/hooks.js');
const padManager = require('./PadManager');
const sessionManager = require('./SessionManager');
const settings = require('../utils/Settings');
@ -47,7 +48,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
*/
exports.checkAccess = async function (padID, sessionCookie, token, userSettings) {
exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;

View file

@ -1,5 +1,7 @@
'use strict';
/**
* The Session Manager provides functions to manage session in the database, it only provides session management for sessions created by the API
* The Session Manager provides functions to manage session in the database,
* it only provides session management for sessions created by the API
*/
/*
@ -18,7 +20,7 @@
* limitations under the License.
*/
const customError = require('../utils/customError');
const CustomError = require('../utils/customError');
const promises = require('../utils/promises');
const randomString = require('../utils/randomstring');
const db = require('./DB');
@ -40,7 +42,8 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
* value is enclosed in double quotes, such as:
*
* Set-Cookie: sessionCookie="s.37cf5299fbf981e14121fba3a588c02b,s.2b21517bf50729d8130ab85736a11346"; Version=1; Path=/; Domain=localhost; Discard
* Set-Cookie: sessionCookie="s.37cf5299fbf981e14121fba3a588c02b,
* s.2b21517bf50729d8130ab85736a11346"; Version=1; Path=/; Domain=localhost; Discard
*
* Where the double quotes at the start and the end of the header value are
* just delimiters. This is perfectly legal: Etherpad parsing logic should
@ -78,26 +81,26 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
return sessionInfo.authorID;
};
exports.doesSessionExist = async function (sessionID) {
exports.doesSessionExist = async (sessionID) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
return (session !== null);
return (session != null);
};
/**
* Creates a new session between an author and a group
*/
exports.createSession = async function (groupID, authorID, validUntil) {
exports.createSession = async (groupID, authorID, validUntil) => {
// check if the group exists
const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) {
throw new customError('groupID does not exist', 'apierror');
throw new CustomError('groupID does not exist', 'apierror');
}
// check if the author exists
const authorExists = await authorManager.doesAuthorExist(authorID);
if (!authorExists) {
throw new customError('authorID does not exist', 'apierror');
throw new CustomError('authorID does not exist', 'apierror');
}
// try to parse validUntil if it's not a number
@ -107,22 +110,22 @@ exports.createSession = async function (groupID, authorID, validUntil) {
// check it's a valid number
if (isNaN(validUntil)) {
throw new customError('validUntil is not a number', 'apierror');
throw new CustomError('validUntil is not a number', 'apierror');
}
// ensure this is not a negative number
if (validUntil < 0) {
throw new customError('validUntil is a negative number', 'apierror');
throw new CustomError('validUntil is a negative number', 'apierror');
}
// ensure this is not a float value
if (!is_int(validUntil)) {
throw new customError('validUntil is a float value', 'apierror');
if (!isInt(validUntil)) {
throw new CustomError('validUntil is a float value', 'apierror');
}
// check if validUntil is in the future
if (validUntil < Math.floor(Date.now() / 1000)) {
throw new customError('validUntil is in the past', 'apierror');
throw new CustomError('validUntil is in the past', 'apierror');
}
// generate sessionID
@ -170,13 +173,13 @@ exports.createSession = async function (groupID, authorID, validUntil) {
return {sessionID};
};
exports.getSessionInfo = async function (sessionID) {
exports.getSessionInfo = async (sessionID) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
if (session == null) {
// session does not exist
throw new customError('sessionID does not exist', 'apierror');
throw new CustomError('sessionID does not exist', 'apierror');
}
// everything is fine, return the sessioninfos
@ -186,11 +189,11 @@ exports.getSessionInfo = async function (sessionID) {
/**
* Deletes a session
*/
exports.deleteSession = async function (sessionID) {
exports.deleteSession = async (sessionID) => {
// ensure that the session exists
const session = await db.get(`session:${sessionID}`);
if (session == null) {
throw new customError('sessionID does not exist', 'apierror');
throw new CustomError('sessionID does not exist', 'apierror');
}
// everything is fine, use the sessioninfos
@ -217,22 +220,22 @@ exports.deleteSession = async function (sessionID) {
}
};
exports.listSessionsOfGroup = async function (groupID) {
exports.listSessionsOfGroup = async (groupID) => {
// check that the group exists
const exists = await groupManager.doesGroupExist(groupID);
if (!exists) {
throw new customError('groupID does not exist', 'apierror');
throw new CustomError('groupID does not exist', 'apierror');
}
const sessions = await listSessionsWithDBKey(`group2sessions:${groupID}`);
return sessions;
};
exports.listSessionsOfAuthor = async function (authorID) {
exports.listSessionsOfAuthor = async (authorID) => {
// check that the author exists
const exists = await authorManager.doesAuthorExist(authorID);
if (!exists) {
throw new customError('authorID does not exist', 'apierror');
throw new CustomError('authorID does not exist', 'apierror');
}
const sessions = await listSessionsWithDBKey(`author2sessions:${authorID}`);
@ -241,7 +244,7 @@ exports.listSessionsOfAuthor = async function (authorID) {
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
// required to return null rather than an empty object if there are none
async function listSessionsWithDBKey(dbkey) {
const listSessionsWithDBKey = async (dbkey) => {
// get the group2sessions entry
const sessionObject = await db.get(dbkey);
const sessions = sessionObject ? sessionObject.sessionIDs : null;
@ -252,7 +255,7 @@ async function listSessionsWithDBKey(dbkey) {
const sessionInfo = await exports.getSessionInfo(sessionID);
sessions[sessionID] = sessionInfo;
} catch (err) {
if (err == 'apierror: sessionID does not exist') {
if (err === 'apierror: sessionID does not exist') {
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
sessions[sessionID] = null;
} else {
@ -262,9 +265,7 @@ async function listSessionsWithDBKey(dbkey) {
}
return sessions;
}
};
// checks if a number is an int
function is_int(value) {
return (parseFloat(value) == parseInt(value)) && !isNaN(value);
}
const isInt = (value) => (parseFloat(value) === parseInt(value)) && !isNaN(value);

View file

@ -1,3 +1,4 @@
'use strict';
/*
* Stores session data in the database
* Source; https://github.com/edy-b/SciFlowWriter/blob/develop/available_plugins/ep_sciflowwriter/db/DirtyStore.js
@ -7,9 +8,9 @@
* express-session, which can't actually use promises anyway.
*/
const DB = require('ep_etherpad-lite/node/db/DB');
const Store = require('ep_etherpad-lite/node_modules/express-session').Store;
const log4js = require('ep_etherpad-lite/node_modules/log4js');
const DB = require('./DB');
const Store = require('express-session').Store;
const log4js = require('log4js');
const logger = log4js.getLogger('SessionStore');

View file

@ -1,6 +1,8 @@
'use strict';
/**
* I found this tests in the old Etherpad and used it to test if the Changeset library can be run on node.js.
* It has no use for ep-lite, but I thought I keep it cause it may help someone to understand the Changeset library
* I found this tests in the old Etherpad and used it to test if the Changeset library can be run on
* node.js. It has no use for ep-lite, but I thought I keep it cause it may help someone to
* understand the Changeset library
* https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2_tests.js
*/
@ -21,52 +23,47 @@
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
const Changeset = require('../static/js/Changeset');
const AttributePool = require('../static/js/AttributePool');
function random() {
this.nextInt = function (maxValue) {
return Math.floor(Math.random() * maxValue);
};
this.nextDouble = function (maxValue) {
return Math.random();
};
this.nextInt = (maxValue) => Math.floor(Math.random() * maxValue);
this.nextDouble = (maxValue) => Math.random();
}
function runTests() {
function print(str) {
const runTests = () => {
const print = (str) => {
console.log(str);
}
};
function assert(code, optMsg) {
if (!eval(code)) throw new Error(`FALSE: ${optMsg || code}`);
}
const assert = (code, optMsg) => {
if (!eval(code)) throw new Error(`FALSE: ${optMsg || code}`); /* eslint-disable-line no-eval */
};
function literal(v) {
const literal = (v) => {
if ((typeof v) === 'string') {
return `"${v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n')}"`;
} else { return JSON.stringify(v); }
}
};
function assertEqualArrays(a, b) {
const assertEqualArrays = (a, b) => {
assert(`JSON.stringify(${literal(a)}) == JSON.stringify(${literal(b)})`);
}
};
function assertEqualStrings(a, b) {
const assertEqualStrings = (a, b) => {
assert(`${literal(a)} == ${literal(b)}`);
}
};
function throughIterator(opsStr) {
const throughIterator = (opsStr) => {
const iter = Changeset.opIterator(opsStr);
const assem = Changeset.opAssembler();
while (iter.hasNext()) {
assem.append(iter.next());
}
return assem.toString();
}
};
function throughSmartAssembler(opsStr) {
const throughSmartAssembler = (opsStr) => {
const iter = Changeset.opIterator(opsStr);
const assem = Changeset.smartOpAssembler();
while (iter.hasNext()) {
@ -74,50 +71,50 @@ function runTests() {
}
assem.endDocument();
return assem.toString();
}
};
(function () {
(() => {
print('> throughIterator');
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
assert(`throughIterator(${literal(x)}) == ${literal(x)}`);
})();
(function () {
(() => {
print('> throughSmartAssembler');
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
assert(`throughSmartAssembler(${literal(x)}) == ${literal(x)}`);
})();
function applyMutations(mu, arrayOfArrays) {
const applyMutations = (mu, arrayOfArrays) => {
arrayOfArrays.forEach((a) => {
const result = mu[a[0]].apply(mu, a.slice(1));
if (a[0] == 'remove' && a[3]) {
if (a[0] === 'remove' && a[3]) {
assertEqualStrings(a[3], result);
}
});
}
};
function mutationsToChangeset(oldLen, arrayOfArrays) {
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
const assem = Changeset.smartOpAssembler();
const op = Changeset.newOp();
const bank = Changeset.stringAssembler();
let oldPos = 0;
let newLen = 0;
arrayOfArrays.forEach((a) => {
if (a[0] == 'skip') {
if (a[0] === 'skip') {
op.opcode = '=';
op.chars = a[1];
op.lines = (a[2] || 0);
assem.append(op);
oldPos += op.chars;
newLen += op.chars;
} else if (a[0] == 'remove') {
} else if (a[0] === 'remove') {
op.opcode = '-';
op.chars = a[1];
op.lines = (a[2] || 0);
assem.append(op);
oldPos += op.chars;
} else if (a[0] == 'insert') {
} else if (a[0] === 'insert') {
op.opcode = '+';
bank.append(a[1]);
op.chars = a[1].length;
@ -129,9 +126,9 @@ function runTests() {
newLen += oldLen - oldPos;
assem.endDocument();
return Changeset.pack(oldLen, newLen, assem.toString(), bank.toString());
}
};
function runMutationTest(testId, origLines, muts, correct) {
const runMutationTest = (testId, origLines, muts, correct) => {
print(`> runMutationTest#${testId}`);
let lines = origLines.slice();
const mu = Changeset.textLinesMutator(lines);
@ -149,7 +146,7 @@ function runTests() {
// print(literal(cs));
const outText = Changeset.applyToText(cs, inText);
assertEqualStrings(correctText, outText);
}
};
runMutationTest(1, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [
['remove', 1, 0, 'a'],
@ -220,7 +217,7 @@ function runTests() {
['skip', 1, 1, true],
], ['banana\n', 'cabbage\n', 'duffle\n']);
function poolOrArray(attribs) {
const poolOrArray = (attribs) => {
if (attribs.getAttrib) {
return attribs; // it's already an attrib pool
} else {
@ -231,23 +228,25 @@ function runTests() {
});
return p;
}
}
};
function runApplyToAttributionTest(testId, attribs, cs, inAttr, outCorrect) {
const runApplyToAttributionTest = (testId, attribs, cs, inAttr, outCorrect) => {
print(`> applyToAttribution#${testId}`);
const p = poolOrArray(attribs);
const result = Changeset.applyToAttribution(
Changeset.checkRep(cs), inAttr, p);
assertEqualStrings(outCorrect, result);
}
};
// turn c<b>a</b>ctus\n into a<b>c</b>tusabcd\n
runApplyToAttributionTest(1, ['bold,', 'bold,true'], 'Z:7>3-1*0=1*1=1=3+4$abcd', '+1*1+1|1+5', '+1*1+1|1+8');
runApplyToAttributionTest(1,
['bold,', 'bold,true'], 'Z:7>3-1*0=1*1=1=3+4$abcd', '+1*1+1|1+5', '+1*1+1|1+8');
// turn "david\ngreenspan\n" into "<b>david\ngreen</b>\n"
runApplyToAttributionTest(2, ['bold,', 'bold,true'], 'Z:g<4*1|1=6*1=5-4$', '|2+g', '*1|1+6*1+5|1+1');
runApplyToAttributionTest(2,
['bold,', 'bold,true'], 'Z:g<4*1|1=6*1=5-4$', '|2+g', '*1|1+6*1+5|1+1');
(function () {
(() => {
print('> mutatorHasMore');
const lines = ['1\n', '2\n', '3\n', '4\n'];
let mu;
@ -288,7 +287,7 @@ function runTests() {
assert(`${mu.hasMore()} == false`);
})();
function runMutateAttributionTest(testId, attribs, cs, alines, outCorrect) {
const runMutateAttributionTest = (testId, attribs, cs, alines, outCorrect) => {
print(`> runMutateAttributionTest#${testId}`);
const p = poolOrArray(attribs);
const alines2 = Array.prototype.slice.call(alines);
@ -298,30 +297,35 @@ function runTests() {
print(`> runMutateAttributionTest#${testId}.applyToAttribution`);
function removeQuestionMarks(a) {
return a.replace(/\?/g, '');
}
const removeQuestionMarks = (a) => a.replace(/\?/g, '');
const inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks));
const correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks));
const mergedResult = Changeset.applyToAttribution(cs, inMerged, p);
assertEqualStrings(correctMerged, mergedResult);
}
};
// turn 123\n 456\n 789\n into 123\n 4<b>5</b>6\n 789\n
runMutateAttributionTest(1, ['bold,true'], 'Z:c>0|1=4=1*0=1$', ['|1+4', '|1+4', '|1+4'], ['|1+4', '+1*0+1|1+2', '|1+4']);
runMutateAttributionTest(1,
['bold,true'], 'Z:c>0|1=4=1*0=1$', ['|1+4', '|1+4', '|1+4'], ['|1+4', '+1*0+1|1+2', '|1+4']);
// make a document bold
runMutateAttributionTest(2, ['bold,true'], 'Z:c>0*0|3=c$', ['|1+4', '|1+4', '|1+4'], ['*0|1+4', '*0|1+4', '*0|1+4']);
runMutateAttributionTest(2,
['bold,true'], 'Z:c>0*0|3=c$', ['|1+4', '|1+4', '|1+4'], ['*0|1+4', '*0|1+4', '*0|1+4']);
// clear bold on document
runMutateAttributionTest(3, ['bold,', 'bold,true'], 'Z:c>0*0|3=c$', ['*1+1+1*1+1|1+1', '+1*1+1|1+2', '*1+1+1*1+1|1+1'], ['|1+4', '|1+4', '|1+4']);
runMutateAttributionTest(3,
['bold,', 'bold,true'], 'Z:c>0*0|3=c$',
['*1+1+1*1+1|1+1', '+1*1+1|1+2', '*1+1+1*1+1|1+1'], ['|1+4', '|1+4', '|1+4']);
// add a character on line 3 of a document with 5 blank lines, and make sure
// the optimization that skips purely-kept lines is working; if any attribution string
// with a '?' is parsed it will cause an error.
runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], 'Z:5>1|2=2+1$x', ['?*1|1+1', '?*2|1+1', '*3|1+1', '?*4|1+1', '?*5|1+1'], ['?*1|1+1', '?*2|1+1', '+1*3|1+1', '?*4|1+1', '?*5|1+1']);
runMutateAttributionTest(4,
['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'],
'Z:5>1|2=2+1$x', ['?*1|1+1', '?*2|1+1', '*3|1+1', '?*4|1+1', '?*5|1+1'],
['?*1|1+1', '?*2|1+1', '+1*3|1+1', '?*4|1+1', '?*5|1+1']);
const testPoolWithChars = (function () {
const testPoolWithChars = (() => {
const p = new AttributePool();
p.putAttrib(['char', 'newline']);
for (let i = 1; i < 36; i++) {
@ -332,39 +336,66 @@ function runTests() {
})();
// based on runMutationTest#1
runMutateAttributionTest(5, testPoolWithChars, 'Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$' + 'tucream\npie\nbot\nbu', ['*a+1*p+2*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1', '*d+1*u+1*f+2*l+1*e+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1'], ['*t+1*u+1*p+1*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '|1+6', '|1+4', '*c+1*a+1*b+1*o+1*t+1*0|1+1', '*b+1*u+1*b+2*a+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1']);
runMutateAttributionTest(5, testPoolWithChars,
'Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$' + 'tucream\npie\nbot\nbu', ['*a+1*p+2*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1', '*d+1*u+1*f+2*l+1*e+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1'], ['*t+1*u+1*p+1*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '|1+6', '|1+4', '*c+1*a+1*b+1*o+1*t+1*0|1+1', '*b+1*u+1*b+2*a+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1']);
// based on runMutationTest#3
runMutateAttributionTest(6, testPoolWithChars, 'Z:11<f|1-6|2=f=6|1-1-8$', ['*a|1+6', '*b|1+7', '*c|1+8', '*d|1+7', '*e|1+9'], ['*b|1+7', '*c|1+8', '*d+6*e|1+1']);
runMutateAttributionTest(6, testPoolWithChars,
'Z:11<f|1-6|2=f=6|1-1-8$', ['*a|1+6', '*b|1+7', '*c|1+8', '*d|1+7', '*e|1+9'],
['*b|1+7', '*c|1+8', '*d+6*e|1+1']);
// based on runMutationTest#4
runMutateAttributionTest(7, testPoolWithChars, 'Z:3>7=1|4+7$\n2\n3\n4\n', ['*1+1*5|1+2'], ['*1+1|1+1', '|1+2', '|1+2', '|1+2', '*5|1+2']);
runMutateAttributionTest(7, testPoolWithChars, 'Z:3>7=1|4+7$\n2\n3\n4\n',
['*1+1*5|1+2'], ['*1+1|1+1', '|1+2', '|1+2', '|1+2', '*5|1+2']);
// based on runMutationTest#5
runMutateAttributionTest(8, testPoolWithChars, 'Z:a<7=1|4-7$', ['*1|1+2', '*2|1+2', '*3|1+2', '*4|1+2', '*5|1+2'], ['*1+1*5|1+2']);
runMutateAttributionTest(8, testPoolWithChars, 'Z:a<7=1|4-7$',
['*1|1+2', '*2|1+2', '*3|1+2', '*4|1+2', '*5|1+2'], ['*1+1*5|1+2']);
// based on runMutationTest#6
runMutateAttributionTest(9, testPoolWithChars, 'Z:k<7*0+1*10|2=8|2-8$0', ['*1+1*2+1*3+1|1+1', '*a+1*b+1*c+1|1+1', '*d+1*e+1*f+1|1+1', '*g+1*h+1*i+1|1+1', '?*x+1*y+1*z+1|1+1'], ['*0+1|1+4', '|1+4', '?*x+1*y+1*z+1|1+1']);
runMutateAttributionTest(9, testPoolWithChars, 'Z:k<7*0+1*10|2=8|2-8$0',
[
'*1+1*2+1*3+1|1+1',
'*a+1*b+1*c+1|1+1',
'*d+1*e+1*f+1|1+1',
'*g+1*h+1*i+1|1+1',
'?*x+1*y+1*z+1|1+1',
],
['*0+1|1+4', '|1+4', '?*x+1*y+1*z+1|1+1']);
runMutateAttributionTest(10, testPoolWithChars, 'Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd', ['|1+3', '|1+3'], ['|1+5', '+2*0+1|1+2']);
runMutateAttributionTest(10, testPoolWithChars, 'Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd',
['|1+3', '|1+3'], ['|1+5', '+2*0+1|1+2']);
runMutateAttributionTest(11, testPoolWithChars, 'Z:s>1|1=4=6|1+1$\n', ['*0|1+4', '*0|1+8', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1'], ['*0|1+4', '*0+6|1+1', '*0|1+2', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1']);
runMutateAttributionTest(11, testPoolWithChars, 'Z:s>1|1=4=6|1+1$\n',
['*0|1+4', '*0|1+8', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1'],
[
'*0|1+4',
'*0+6|1+1',
'*0|1+2',
'*0+5|1+1',
'*0|1+1',
'*0|1+5',
'*0|1+1',
'*0|1+1',
'*0|1+1',
'|1+1',
]);
function randomInlineString(len, rand) {
const randomInlineString = (len, rand) => {
const assem = Changeset.stringAssembler();
for (let i = 0; i < len; i++) {
assem.append(String.fromCharCode(rand.nextInt(26) + 97));
}
return assem.toString();
}
};
function randomMultiline(approxMaxLines, approxMaxCols, rand) {
const randomMultiline = (approxMaxLines, approxMaxCols, rand) => {
const numParts = rand.nextInt(approxMaxLines * 2) + 1;
const txt = Changeset.stringAssembler();
txt.append(rand.nextInt(2) ? '\n' : '');
for (let i = 0; i < numParts; i++) {
if ((i % 2) == 0) {
if ((i % 2) === 0) {
if (rand.nextInt(10)) {
txt.append(randomInlineString(rand.nextInt(approxMaxCols) + 1, rand));
} else {
@ -375,9 +406,9 @@ function runTests() {
}
}
return txt.toString();
}
};
function randomStringOperation(numCharsLeft, rand) {
const randomStringOperation = (numCharsLeft, rand) => {
let result;
switch (rand.nextInt(9)) {
case 0:
@ -476,26 +507,26 @@ function runTests() {
result.skip = Math.min(result.skip, maxOrig);
}
return result;
}
};
function randomTwoPropAttribs(opcode, rand) {
const randomTwoPropAttribs = (opcode, rand) => {
// assumes attrib pool like ['apple,','apple,true','banana,','banana,true']
if (opcode == '-' || rand.nextInt(3)) {
if (opcode === '-' || rand.nextInt(3)) {
return '';
} else if (rand.nextInt(3)) {
if (opcode == '+' || rand.nextInt(2)) {
if (opcode === '+' || rand.nextInt(2)) {
return `*${Changeset.numToString(rand.nextInt(2) * 2 + 1)}`;
} else {
return `*${Changeset.numToString(rand.nextInt(2) * 2)}`;
}
} else if (opcode == '+' || rand.nextInt(4) == 0) {
} else if (opcode === '+' || rand.nextInt(4) === 0) {
return '*1*3';
} else {
return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)];
}
}
};
function randomTestChangeset(origText, rand, withAttribs) {
const randomTestChangeset = (origText, rand, withAttribs) => {
const charBank = Changeset.stringAssembler();
let textLeft = origText; // always keep final newline
const outTextAssem = Changeset.stringAssembler();
@ -504,13 +535,13 @@ function runTests() {
const nextOp = Changeset.newOp();
function appendMultilineOp(opcode, txt) {
const appendMultilineOp = (opcode, txt) => {
nextOp.opcode = opcode;
if (withAttribs) {
nextOp.attribs = randomTwoPropAttribs(opcode, rand);
}
txt.replace(/\n|[^\n]+/g, (t) => {
if (t == '\n') {
if (t === '\n') {
nextOp.chars = 1;
nextOp.lines = 1;
opAssem.append(nextOp);
@ -521,26 +552,26 @@ function runTests() {
}
return '';
});
}
};
function doOp() {
const doOp = () => {
const o = randomStringOperation(textLeft.length, rand);
if (o.insert) {
var txt = o.insert;
const txt = o.insert;
charBank.append(txt);
outTextAssem.append(txt);
appendMultilineOp('+', txt);
} else if (o.skip) {
var txt = textLeft.substring(0, o.skip);
const txt = textLeft.substring(0, o.skip);
textLeft = textLeft.substring(o.skip);
outTextAssem.append(txt);
appendMultilineOp('=', txt);
} else if (o.remove) {
var txt = textLeft.substring(0, o.remove);
const txt = textLeft.substring(0, o.remove);
textLeft = textLeft.substring(o.remove);
appendMultilineOp('-', txt);
}
}
};
while (textLeft.length > 1) doOp();
for (let i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen)
@ -549,9 +580,9 @@ function runTests() {
const cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString());
Changeset.checkRep(cs);
return [cs, outText];
}
};
function testCompose(randomSeed) {
const testCompose = (randomSeed) => {
const rand = new random();
print(`> testCompose#${randomSeed}`);
@ -583,9 +614,9 @@ function runTests() {
assertEqualStrings(text2, Changeset.applyToText(change12, startText));
assertEqualStrings(text3, Changeset.applyToText(change23, text1));
assertEqualStrings(text3, Changeset.applyToText(change123, startText));
}
};
for (var i = 0; i < 30; i++) testCompose(i);
for (let i = 0; i < 30; i++) testCompose(i);
(function simpleComposeAttributesTest() {
print('> simpleComposeAttributesTest');
@ -607,12 +638,12 @@ function runTests() {
p.putAttrib(['y', 'abc']);
p.putAttrib(['y', 'def']);
function testFollow(a, b, afb, bfa, merge) {
const testFollow = (a, b, afb, bfa, merge) => {
assertEqualStrings(afb, Changeset.followAttributes(a, b, p));
assertEqualStrings(bfa, Changeset.followAttributes(b, a, p));
assertEqualStrings(merge, Changeset.composeAttributes(a, afb, true, p));
assertEqualStrings(merge, Changeset.composeAttributes(b, bfa, true, p));
}
};
testFollow('', '', '', '', '');
testFollow('*0', '', '', '*0', '*0');
@ -624,7 +655,7 @@ function runTests() {
testFollow('*0*4', '*2', '', '*0*4', '*0*4');
})();
function testFollow(randomSeed) {
const testFollow = (randomSeed) => {
const rand = new random();
print(`> testFollow#${randomSeed}`);
@ -642,37 +673,37 @@ function runTests() {
const merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa));
assertEqualStrings(merge1, merge2);
}
};
for (var i = 0; i < 30; i++) testFollow(i);
for (let i = 0; i < 30; i++) testFollow(i);
function testSplitJoinAttributionLines(randomSeed) {
const testSplitJoinAttributionLines = (randomSeed) => {
const rand = new random();
print(`> testSplitJoinAttributionLines#${randomSeed}`);
const doc = `${randomMultiline(10, 20, rand)}\n`;
function stringToOps(str) {
const stringToOps = (str) => {
const assem = Changeset.mergingOpAssembler();
const o = Changeset.newOp('+');
o.chars = 1;
for (let i = 0; i < str.length; i++) {
const c = str.charAt(i);
o.lines = (c == '\n' ? 1 : 0);
o.attribs = (c == 'a' || c == 'b' ? `*${c}` : '');
o.lines = (c === '\n' ? 1 : 0);
o.attribs = (c === 'a' || c === 'b' ? `*${c}` : '');
assem.append(o);
}
return assem.toString();
}
};
const theJoined = stringToOps(doc);
const theSplit = doc.match(/[^\n]*\n/g).map(stringToOps);
assertEqualArrays(theSplit, Changeset.splitAttributionLines(theJoined, doc));
assertEqualStrings(theJoined, Changeset.joinAttributionLines(theSplit));
}
};
for (var i = 0; i < 10; i++) testSplitJoinAttributionLines(i);
for (let i = 0; i < 10; i++) testSplitJoinAttributionLines(i);
(function testMoveOpsToNewPool() {
print('> testMoveOpsToNewPool');
@ -685,8 +716,10 @@ function runTests() {
pool2.putAttrib(['foo', 'bar']);
assertEqualStrings(Changeset.moveOpsToNewPool('Z:1>2*1+1*0+1$ab', pool1, pool2), 'Z:1>2*0+1*1+1$ab');
assertEqualStrings(Changeset.moveOpsToNewPool('*1+1*0+1', pool1, pool2), '*0+1*1+1');
assertEqualStrings(
Changeset.moveOpsToNewPool('Z:1>2*1+1*0+1$ab', pool1, pool2), 'Z:1>2*0+1*1+1$ab');
assertEqualStrings(
Changeset.moveOpsToNewPool('*1+1*0+1', pool1, pool2), '*0+1*1+1');
})();
@ -709,14 +742,15 @@ function runTests() {
assertEqualArrays(correctSplices, Changeset.toSplices(cs));
})();
function testCharacterRangeFollow(testId, cs, oldRange, insertionsAfter, correctNewRange) {
const testCharacterRangeFollow = (testId, cs, oldRange, insertionsAfter, correctNewRange) => {
print(`> testCharacterRangeFollow#${testId}`);
cs = Changeset.checkRep(cs);
assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(
cs, oldRange[0], oldRange[1], insertionsAfter));
};
var cs = Changeset.checkRep(cs);
assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter));
}
testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk', [7, 10], false, [14, 15]);
testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk',
[7, 10], false, [14, 15]);
testCharacterRangeFollow(2, 'Z:bc<6|x=b4|2-6$', [400, 407], false, [400, 401]);
testCharacterRangeFollow(3, 'Z:4>0-3+3$abc', [0, 3], false, [3, 3]);
testCharacterRangeFollow(4, 'Z:4>0-3+3$abc', [0, 3], true, [0, 0]);
@ -735,23 +769,31 @@ function runTests() {
p.putAttrib(['name', 'david']);
p.putAttrib(['color', 'green']);
assertEqualStrings('david', Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p));
assertEqualStrings('david', Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p));
assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p));
assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p));
assertEqualStrings('green', Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p));
assertEqualStrings('green', Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p));
assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p));
assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p));
assertEqualStrings('david',
Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p));
assertEqualStrings('david',
Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p));
assertEqualStrings('',
Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p));
assertEqualStrings('',
Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p));
assertEqualStrings('green',
Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p));
assertEqualStrings('green',
Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p));
assertEqualStrings('',
Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p));
assertEqualStrings('',
Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p));
})();
function testAppendATextToAssembler(testId, atext, correctOps) {
const testAppendATextToAssembler = (testId, atext, correctOps) => {
print(`> testAppendATextToAssembler#${testId}`);
const assem = Changeset.smartOpAssembler();
Changeset.appendATextToAssembler(atext, assem);
assertEqualStrings(correctOps, assem.toString());
}
};
testAppendATextToAssembler(1, {
text: '\n',
@ -786,13 +828,13 @@ function runTests() {
attribs: '|2+2*x|2+5',
}, '|2+2*x|1+1*x+3');
function testMakeAttribsString(testId, pool, opcode, attribs, correctString) {
const testMakeAttribsString = (testId, pool, opcode, attribs, correctString) => {
print(`> testMakeAttribsString#${testId}`);
const p = poolOrArray(pool);
const str = Changeset.makeAttribsString(opcode, attribs, p);
assertEqualStrings(correctString, str);
}
};
testMakeAttribsString(1, ['bold,'], '+', [
['bold', ''],
@ -809,12 +851,12 @@ function runTests() {
['abc', 'def'],
], '*0*1');
function testSubattribution(testId, astr, start, end, correctOutput) {
const testSubattribution = (testId, astr, start, end, correctOutput) => {
print(`> testSubattribution#${testId}`);
const str = Changeset.subattribution(astr, start, end);
assertEqualStrings(correctOutput, str);
}
};
testSubattribution(1, '+1', 0, 0, '');
testSubattribution(2, '+1', 0, 1, '+1');
@ -859,39 +901,42 @@ function runTests() {
testSubattribution(41, '*0+2+1*1|1+3', 2, 6, '+1*1|1+3');
testSubattribution(42, '*0+2+1*1+3', 2, 6, '+1*1+3');
function testFilterAttribNumbers(testId, cs, filter, correctOutput) {
const testFilterAttribNumbers = (testId, cs, filter, correctOutput) => {
print(`> testFilterAttribNumbers#${testId}`);
const str = Changeset.filterAttribNumbers(cs, filter);
assertEqualStrings(correctOutput, str);
}
};
testFilterAttribNumbers(1, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6', (n) => (n % 2) == 0, '*0+1+2+3+4*2+5*0*2*c+6');
testFilterAttribNumbers(2, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6', (n) => (n % 2) == 1, '*1+1+2+3*1+4+5*1*b+6');
testFilterAttribNumbers(1, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6',
(n) => (n % 2) === 0, '*0+1+2+3+4*2+5*0*2*c+6');
testFilterAttribNumbers(2, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6',
(n) => (n % 2) === 1, '*1+1+2+3*1+4+5*1*b+6');
function testInverse(testId, cs, lines, alines, pool, correctOutput) {
const testInverse = (testId, cs, lines, alines, pool, correctOutput) => {
print(`> testInverse#${testId}`);
pool = poolOrArray(pool);
const str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool);
assertEqualStrings(correctOutput, str);
}
};
// take "FFFFTTTTT" and apply "-FT--FFTT", the inverse of which is "--F--TT--"
testInverse(1, 'Z:9>0=1*0=1*1=1=2*0=2*1|1=2$', null, ['+4*1+5'], ['bold,', 'bold,true'], 'Z:9>0=2*0=1=2*1=2$');
testInverse(1, 'Z:9>0=1*0=1*1=1=2*0=2*1|1=2$', null,
['+4*1+5'], ['bold,', 'bold,true'], 'Z:9>0=2*0=1=2*1=2$');
function testMutateTextLines(testId, cs, lines, correctLines) {
const testMutateTextLines = (testId, cs, lines, correctLines) => {
print(`> testMutateTextLines#${testId}`);
const a = lines.slice();
Changeset.mutateTextLines(cs, a);
assertEqualArrays(correctLines, a);
}
};
testMutateTextLines(1, 'Z:4<1|1-2-1|1+1+1$\nc', ['a\n', 'b\n'], ['\n', 'c\n']);
testMutateTextLines(2, 'Z:4>0|1-2-1|2+3$\nc\n', ['a\n', 'b\n'], ['\n', 'c\n', '\n']);
function testInverseRandom(randomSeed) {
const testInverseRandom = (randomSeed) => {
const rand = new random();
print(`> testInverseRandom#${randomSeed}`);
@ -928,9 +973,9 @@ function runTests() {
// print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n'));
assertEqualArrays(origLines, lines);
assertEqualArrays(origALines, alines);
}
};
for (var i = 0; i < 30; i++) testInverseRandom(i);
}
for (let i = 0; i < 30; i++) testInverseRandom(i);
};
runTests();

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The API Handler handles all API http requests
*/
@ -37,7 +38,8 @@ try {
apikey = fs.readFileSync(apikeyFilename, 'utf8');
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
} catch (e) {
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apiHandlerLogger.info(
`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename, apikey, 'utf8');
}

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Handles the export requests
*/
@ -25,7 +26,7 @@ const exportEtherpad = require('../utils/ExportEtherpad');
const fs = require('fs');
const settings = require('../utils/Settings');
const os = require('os');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const hooks = require('../../static/js/pluginfw/hooks');
const TidyHtml = require('../utils/TidyHtml');
const util = require('util');
@ -49,7 +50,7 @@ const tempDirectory = os.tmpdir();
/**
* do a requested export
*/
async function doExport(req, res, padId, readOnlyId, type) {
const doExport = async (req, res, padId, readOnlyId, type) => {
// avoid naming the read-only file as the original pad's id
let fileName = readOnlyId ? readOnlyId : padId;
@ -104,7 +105,6 @@ async function doExport(req, res, padId, readOnlyId, type) {
const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res});
if (result.length > 0) {
// console.log("export handled by plugin", destFile);
handledByPlugin = true;
} else {
// @TODO no Promise interface for convertors (yet)
await new Promise((resolve, reject) => {
@ -115,7 +115,6 @@ async function doExport(req, res, padId, readOnlyId, type) {
}
// send the file
const sendFile = util.promisify(res.sendFile);
await res.sendFile(destFile, null);
// clean up temporary files
@ -128,9 +127,9 @@ async function doExport(req, res, padId, readOnlyId, type) {
await fsp_unlink(destFile);
}
}
};
exports.doExport = function (req, res, padId, readOnlyId, type) {
exports.doExport = (req, res, padId, readOnlyId, type) => {
doExport(req, res, padId, readOnlyId, type).catch((err) => {
if (err !== 'stop') {
throw err;

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Handles the import requests
*/
@ -30,7 +31,7 @@ const os = require('os');
const importHtml = require('../utils/ImportHtml');
const importEtherpad = require('../utils/ImportEtherpad');
const log4js = require('log4js');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const hooks = require('../../static/js/pluginfw/hooks.js');
const util = require('util');
const fsp_exists = util.promisify(fs.exists);
@ -42,7 +43,7 @@ let convertor = null;
let exportExtension = 'htm';
// load abiword only if it is enabled and if soffice is disabled
if (settings.abiword != null && settings.soffice === null) {
if (settings.abiword != null && settings.soffice == null) {
convertor = require('../utils/Abiword');
}
@ -57,7 +58,7 @@ const tmpDirectory = os.tmpdir();
/**
* do a requested import
*/
async function doImport(req, res, padId) {
const doImport = async (req, res, padId) => {
const apiLogger = log4js.getLogger('ImportHandler');
// pipe to a file
@ -112,7 +113,8 @@ async function doImport(req, res, padId) {
// ensure this is a file ending we know, else we change the file ending to .txt
// this allows us to accept source code files like .c or .java
const fileEnding = path.extname(srcFile).toLowerCase();
const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
const knownFileEndings =
['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
if (fileEndingUnknown) {
@ -146,7 +148,7 @@ async function doImport(req, res, padId) {
const headCount = _pad.head;
if (headCount >= 10) {
apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this");
apiLogger.warn('Aborting direct database import attempt of a pad that already has content');
throw 'padHasData';
}
@ -251,9 +253,9 @@ async function doImport(req, res, padId) {
if (await fsp_exists(destFile)) {
fsp_unlink(destFile);
}
}
};
exports.doImport = function (req, res, padId) {
exports.doImport = (req, res, padId) => {
/**
* NB: abuse the 'req' object by storing an additional
* 'directDatabaseAccess' property on it so that it can
@ -266,7 +268,10 @@ exports.doImport = function (req, res, padId) {
let status = 'ok';
doImport(req, res, padId).catch((err) => {
// check for known errors and replace the status
if (err == 'uploadFailed' || err == 'convertFailed' || err == 'padHasData' || err == 'maxFileSize') {
if (err === 'uploadFailed' ||
err === 'convertFailed' ||
err === 'padHasData' ||
err === 'maxFileSize') {
status = err;
} else {
throw err;

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
*/
@ -18,22 +19,20 @@
* limitations under the License.
*/
/* global exports, process, require */
const padManager = require('../db/PadManager');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
const AttributeManager = require('ep_etherpad-lite/static/js/AttributeManager');
const Changeset = require('../../static/js/Changeset');
const AttributePool = require('../../static/js/AttributePool');
const AttributeManager = require('../../static/js/AttributeManager');
const authorManager = require('../db/AuthorManager');
const readOnlyManager = require('../db/ReadOnlyManager');
const settings = require('../utils/Settings');
const securityManager = require('../db/SecurityManager');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js');
const plugins = require('../../static/js/pluginfw/plugin_defs.js');
const log4js = require('log4js');
const messageLogger = log4js.getLogger('message');
const accessLogger = log4js.getLogger('access');
const _ = require('underscore');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const hooks = require('../../static/js/pluginfw/hooks.js');
const channels = require('channels');
const stats = require('../stats');
const assert = require('assert').strict;
@ -65,7 +64,9 @@ stats.gauge('totalUsers', () => Object.keys(socketio.sockets.sockets).length);
/**
* A changeset queue per pad that is processed by handleUserChanges()
*/
const padChannels = new channels.channels(({socket, message}, callback) => nodeify(handleUserChanges(socket, message), callback));
const padChannels = new channels.channels(
({socket, message}, callback) => nodeify(handleUserChanges(socket, message), callback)
);
/**
* Saves the Socket class we need to send and receive data from the client
@ -76,7 +77,7 @@ let socketio;
* This Method is called by server.js to tell the message handler on which socket it should send
* @param socket_io The Socket
*/
exports.setSocketIO = function (socket_io) {
exports.setSocketIO = (socket_io) => {
socketio = socket_io;
};
@ -94,7 +95,7 @@ exports.handleConnect = (socket) => {
/**
* Kicks all sessions from a pad
*/
exports.kickSessionsFromPad = function (padID) {
exports.kickSessionsFromPad = (padID) => {
if (typeof socketio.sockets.clients !== 'function') return;
// skip if there is nobody on this pad
@ -114,7 +115,8 @@ exports.handleDisconnect = async (socket) => {
// save the padname of this session
const session = sessioninfos[socket.id];
// if this connection was already etablished with a handshake, send a disconnect message to the others
// if this connection was already etablished with a handshake,
// send a disconnect message to the others
if (session && session.author) {
const {session: {user} = {}} = socket.client.request;
accessLogger.info(`${'[LEAVE]' +
@ -192,7 +194,8 @@ exports.handleMessage = async (socket, message) => {
const auth = thisSession.auth;
if (!auth) {
console.error('Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.');
console.error('Auth was never applied to a session. If you are using the ' +
'stress-test tool then restart Etherpad and the Stress test tool.');
return;
}
@ -234,7 +237,7 @@ exports.handleMessage = async (socket, message) => {
}
// Call handleMessage hook. If a plugin returns null, the message will be dropped.
if ((await hooks.aCallAll('handleMessage', context)).some((m) => m === null)) {
if ((await hooks.aCallAll('handleMessage', context)).some((m) => m == null)) {
return;
}
@ -283,11 +286,11 @@ exports.handleMessage = async (socket, message) => {
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleSaveRevisionMessage(socket, message) {
const handleSaveRevisionMessage = async (socket, message) => {
const {padId, author: authorId} = sessioninfos[socket.id];
const pad = await padManager.getPad(padId);
await pad.addSavedRevision(pad.head, authorId);
}
};
/**
* Handles a custom message, different to the function below as it handles
@ -296,7 +299,7 @@ async function handleSaveRevisionMessage(socket, message) {
* @param msg {Object} the message we're sending
* @param sessionID {string} the socketIO session to which we're sending this message
*/
exports.handleCustomObjectMessage = function (msg, sessionID) {
exports.handleCustomObjectMessage = (msg, sessionID) => {
if (msg.data.type === 'CUSTOM') {
if (sessionID) {
// a sessionID is targeted: directly to this sessionID
@ -314,7 +317,7 @@ exports.handleCustomObjectMessage = function (msg, sessionID) {
* @param padID {Pad} the pad to which we're sending this message
* @param msgString {String} the message we're sending
*/
exports.handleCustomMessage = function (padID, msgString) {
exports.handleCustomMessage = (padID, msgString) => {
const time = Date.now();
const msg = {
type: 'COLLABROOM',
@ -331,12 +334,12 @@ exports.handleCustomMessage = function (padID, msgString) {
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleChatMessage(socket, message) {
const handleChatMessage = async (socket, message) => {
const time = Date.now();
const text = message.data.text;
const {padId, author: authorId} = sessioninfos[socket.id];
await exports.sendChatMessageToPadClients(time, authorId, text, padId);
}
};
/**
* Sends a chat message to all clients of this pad
@ -345,7 +348,7 @@ async function handleChatMessage(socket, message) {
* @param text the text of the chat message
* @param padId the padId to send the chat message to
*/
exports.sendChatMessageToPadClients = async function (time, userId, text, padId) {
exports.sendChatMessageToPadClients = async (time, userId, text, padId) => {
// get the pad
const pad = await padManager.getPad(padId);
@ -371,7 +374,7 @@ exports.sendChatMessageToPadClients = async function (time, userId, text, padId)
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleGetChatMessages(socket, message) {
const handleGetChatMessages = async (socket, message) => {
if (message.data.start == null) {
messageLogger.warn('Dropped message, GetChatMessages Message has no start!');
return;
@ -387,7 +390,8 @@ async function handleGetChatMessages(socket, message) {
const count = end - start;
if (count < 0 || count > 100) {
messageLogger.warn('Dropped message, GetChatMessages Message, client requested invalid amount of messages!');
messageLogger.warn(
'Dropped message, GetChatMessages Message, client requested invalid amount of messages!');
return;
}
@ -405,14 +409,14 @@ async function handleGetChatMessages(socket, message) {
// send the messages back to the client
socket.json.send(infoMsg);
}
};
/**
* Handles a handleSuggestUserName, that means a user have suggest a userName for a other user
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
function handleSuggestUserName(socket, message) {
const handleSuggestUserName = (socket, message) => {
// check if all ok
if (message.data.payload.newName == null) {
messageLogger.warn('Dropped message, suggestUserName Message has no newName!');
@ -433,14 +437,15 @@ function handleSuggestUserName(socket, message) {
socket.json.send(message);
}
});
}
};
/**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name.
* Anyway, we get both informations
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleUserInfoUpdate(socket, message) {
const handleUserInfoUpdate = async (socket, message) => {
// check if all ok
if (message.data.userInfo == null) {
messageLogger.warn('Dropped message, USERINFO_UPDATE Message has no userInfo!');
@ -463,7 +468,8 @@ async function handleUserInfoUpdate(socket, message) {
const author = session.author;
// Check colorId is a Hex color
const isColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(message.data.userInfo.colorId); // for #f00 (Thanks Smamatti)
// for #f00 (Thanks Smamatti)
const isColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(message.data.userInfo.colorId);
if (!isColor) {
messageLogger.warn(`Dropped message, USERINFO_UPDATE Color is malformed.${message.data}`);
return;
@ -496,7 +502,7 @@ async function handleUserInfoUpdate(socket, message) {
// Block until the authorManager has stored the new attributes.
await p;
}
};
/**
* Handles a USER_CHANGES message, where the client submits its local
@ -512,7 +518,7 @@ async function handleUserInfoUpdate(socket, message) {
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleUserChanges(socket, message) {
const handleUserChanges = async (socket, message) => {
// This one's no longer pending, as we're gonna process it now
stats.counter('pendingEdits').dec();
@ -578,7 +584,8 @@ async function handleUserChanges(socket, message) {
// + can add text with attribs
// = can change or add attribs
// - can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool
// - can have attribs, but they are discarded and don't show up in the attribs -
// but do show up in the pool
op.attribs.split('*').forEach((attr) => {
if (!attr) return;
@ -586,9 +593,11 @@ async function handleUserChanges(socket, message) {
attr = wireApool.getAttrib(attr);
if (!attr) return;
// the empty author is used in the clearAuthorship functionality so this should be the only exception
// the empty author is used in the clearAuthorship functionality so this
// should be the only exception
if ('author' === attr[0] && (attr[1] !== thisSession.author && attr[1] !== '')) {
throw new Error(`Author ${thisSession.author} tried to submit changes as author ${attr[1]} in changeset ${changeset}`);
throw new Error(`Author ${thisSession.author} tried to submit changes as author ` +
`${attr[1]} in changeset ${changeset}`);
}
});
}
@ -628,7 +637,7 @@ async function handleUserChanges(socket, message) {
if (baseRev + 1 === r && c === changeset) {
socket.json.send({disconnect: 'badChangeset'});
stats.meter('failedChangesets').mark();
throw new Error("Won't apply USER_CHANGES, because it contains an already accepted changeset");
throw new Error("Won't apply USER_CHANGES, as it contains an already accepted changeset");
}
changeset = Changeset.follow(c, changeset, false, apool);
@ -672,9 +681,9 @@ async function handleUserChanges(socket, message) {
}
stopWatch.end();
}
};
exports.updatePadClients = async function (pad) {
exports.updatePadClients = async (pad) => {
// skip this if no-one is on this pad
const roomSockets = _getRoomSockets(pad.id);
if (roomSockets.length === 0) return;
@ -682,9 +691,12 @@ exports.updatePadClients = async function (pad) {
// since all clients usually get the same set of changesets, store them in local cache
// to remove unnecessary roundtrip to the datalayer
// NB: note below possibly now accommodated via the change to promises/async
// TODO: in REAL world, if we're working without datalayer cache, all requests to revisions will be fired
// BEFORE first result will be landed to our cache object. The solution is to replace parallel processing
// via async.forEach with sequential for() loop. There is no real benefits of running this in parallel,
// TODO: in REAL world, if we're working without datalayer cache,
// all requests to revisions will be fired
// BEFORE first result will be landed to our cache object.
// The solution is to replace parallel processing
// via async.forEach with sequential for() loop. There is no real
// benefits of running this in parallel,
// but benefit of reusing cached revision object is HUGE
const revCache = {};
@ -737,7 +749,7 @@ exports.updatePadClients = async function (pad) {
/**
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
*/
function _correctMarkersInPad(atext, apool) {
const _correctMarkersInPad = (atext, apool) => {
const text = atext.text;
// collect char positions of line markers (e.g. bullets) in new atext
@ -746,9 +758,11 @@ function _correctMarkersInPad(atext, apool) {
const iter = Changeset.opIterator(atext.attribs);
let offset = 0;
while (iter.hasNext()) {
var op = iter.next();
const op = iter.next();
const hasMarker = _.find(AttributeManager.lineAttributes, (attribute) => Changeset.opAttributeValue(op, attribute, apool)) !== undefined;
const hasMarker = _.find(
AttributeManager.lineAttributes,
(attribute) => Changeset.opAttributeValue(op, attribute, apool)) !== undefined;
if (hasMarker) {
for (let i = 0; i < op.chars; i++) {
@ -778,9 +792,9 @@ function _correctMarkersInPad(atext, apool) {
});
return builder.toString();
}
};
async function handleSwitchToPad(socket, message, _authorID) {
const handleSwitchToPad = async (socket, message, _authorID) => {
const currentSessionInfo = sessioninfos[socket.id];
const padId = currentSessionInfo.padId;
@ -816,10 +830,10 @@ async function handleSwitchToPad(socket, message, _authorID) {
const newSessionInfo = sessioninfos[socket.id];
createSessionInfoAuth(newSessionInfo, message);
await handleClientReady(socket, message, authorID);
}
};
// Creates/replaces the auth object in the given session info.
function createSessionInfoAuth(sessionInfo, message) {
const createSessionInfoAuth = (sessionInfo, message) => {
// Remember this information since we won't
// have the cookie in further socket.io messages.
// This information will be used to check if
@ -830,15 +844,16 @@ function createSessionInfoAuth(sessionInfo, message) {
padID: message.padId,
token: message.token,
};
}
};
/**
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client
* to the server. The Client sends his token
* and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
async function handleClientReady(socket, message, authorID) {
const handleClientReady = async (socket, message, authorID) => {
// check if all ok
if (!message.token) {
messageLogger.warn('Dropped message, CLIENT_READY Message has no token!');
@ -884,9 +899,11 @@ async function handleClientReady(socket, message, authorID) {
const historicalAuthorData = {};
await Promise.all(authors.map((authorId) => authorManager.getAuthor(authorId).then((author) => {
if (!author) {
messageLogger.error('There is no author for authorId: ', authorId, '. This is possibly related to https://github.com/ether/etherpad-lite/issues/2802');
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
'This is possibly related to https://github.com/ether/etherpad-lite/issues/2802');
} else {
historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients)
// Filter author attribs (e.g. don't send author's pads to all clients)
historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId};
}
})));
@ -931,7 +948,8 @@ async function handleClientReady(socket, message, authorID) {
// Save the revision in sessioninfos, we take the revision from the info the client send to us
sessionInfo.rev = message.client_rev;
// During the client reconnect, client might miss some revisions from other clients. By using client revision,
// During the client reconnect, client might miss some revisions from other clients.
// By using client revision,
// this below code sends all the revisions missed during the client reconnect
const revisionsNeeded = [];
const changesets = {};
@ -987,12 +1005,13 @@ async function handleClientReady(socket, message, authorID) {
}
} else {
// This is a normal first connect
let atext;
let apool;
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
try {
var atext = Changeset.cloneAText(pad.atext);
atext = Changeset.cloneAText(pad.atext);
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
var apool = attribsForWire.pool.toJsonable();
apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated;
} catch (e) {
console.error(e.stack || e);
@ -1147,12 +1166,12 @@ async function handleClientReady(socket, message, authorID) {
socket.json.send(msg);
}));
}
}
};
/**
* Handles a request for a rough changeset, the timeslider client needs it
*/
async function handleChangesetRequest(socket, message) {
const handleChangesetRequest = async (socket, message) => {
// check if all ok
if (message.data == null) {
messageLogger.warn('Dropped message, changeset request has no data!');
@ -1197,15 +1216,16 @@ async function handleChangesetRequest(socket, message) {
data.requestID = message.data.requestID;
socket.json.send({type: 'CHANGESET_REQ', data});
} catch (err) {
console.error(`Error while handling a changeset request for ${padIds.padId}`, err.toString(), message.data);
}
console.error(`Error while handling a changeset request for ${padIds.padId}`,
err.toString(), message.data);
}
};
/**
* Tries to rebuild the getChangestInfo function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
*/
async function getChangesetInfo(padId, startNum, endNum, granularity) {
const getChangesetInfo = async (padId, startNum, endNum, granularity) => {
const pad = await padManager.getPad(padId);
const head_revision = pad.getHeadRevisionNumber();
@ -1237,15 +1257,25 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) {
// get all needed composite Changesets
const composedChangesets = {};
const p1 = Promise.all(compositesChangesetNeeded.map((item) => composePadChangesets(padId, item.start, item.end).then((changeset) => {
const p1 = Promise.all(
compositesChangesetNeeded.map(
(item) => composePadChangesets(
padId, item.start, item.end
).then(
(changeset) => {
composedChangesets[`${item.start}/${item.end}`] = changeset;
})));
}
)
)
);
// get all needed revision Dates
const revisionDate = [];
const p2 = Promise.all(revTimesNeeded.map((revNum) => pad.getRevisionDate(revNum).then((revDate) => {
const p2 = Promise.all(revTimesNeeded.map((revNum) => pad.getRevisionDate(revNum)
.then((revDate) => {
revisionDate[revNum] = Math.floor(revDate / 1000);
})));
})
));
// get the lines
let lines;
@ -1288,13 +1318,13 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) {
return {forwardsChangesets, backwardsChangesets,
apool: apool.toJsonable(), actualEndNum: endNum,
timeDeltas, start: startNum, granularity};
}
};
/**
* Tries to rebuild the getPadLines function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
*/
async function getPadLines(padId, revNum) {
const getPadLines = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
// get the atext
@ -1310,13 +1340,13 @@ async function getPadLines(padId, revNum) {
textlines: Changeset.splitTextLines(atext.text),
alines: Changeset.splitAttributionLines(atext.attribs, atext.text),
};
}
};
/**
* Tries to rebuild the composePadChangeset function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
*/
async function composePadChangesets(padId, startNum, endNum) {
const composePadChangesets = async (padId, startNum, endNum) => {
const pad = await padManager.getPad(padId);
// fetch all changesets we need
@ -1333,7 +1363,9 @@ async function composePadChangesets(padId, startNum, endNum) {
// get all changesets
const changesets = {};
await Promise.all(changesetsNeeded.map((revNum) => pad.getRevisionChangeset(revNum).then((changeset) => changesets[revNum] = changeset)));
await Promise.all(changesetsNeeded.map(
(revNum) => pad.getRevisionChangeset(revNum).then((changeset) => changesets[revNum] = changeset)
));
// compose Changesets
let r;
@ -1351,9 +1383,9 @@ async function composePadChangesets(padId, startNum, endNum) {
console.warn('failed to compose cs in pad:', padId, ' startrev:', startNum, ' current rev:', r);
throw e;
}
}
};
function _getRoomSockets(padID) {
const _getRoomSockets = (padID) => {
const roomSockets = [];
const room = socketio.sockets.adapter.rooms[padID];
@ -1364,21 +1396,19 @@ function _getRoomSockets(padID) {
}
return roomSockets;
}
};
/**
* Get the number of users in a pad
*/
exports.padUsersCount = function (padID) {
return {
exports.padUsersCount = (padID) => ({
padUsersCount: _getRoomSockets(padID).length,
};
};
});
/**
* Get the list of users in a pad
*/
exports.padUsers = async function (padID) {
exports.padUsers = async (padID) => {
const padUsers = [];
// iterate over all clients (in parallel)

View file

@ -1,3 +1,4 @@
'use strict';
/**
* This is the Socket.IO Router. It routes the Messages between the
* components of the Server. The components are at the moment: pad and timeslider
@ -21,9 +22,6 @@
const log4js = require('log4js');
const messageLogger = log4js.getLogger('message');
const securityManager = require('../db/SecurityManager');
const readOnlyManager = require('../db/ReadOnlyManager');
const settings = require('../utils/Settings');
/**
* Saves all components
@ -37,7 +35,7 @@ let socket;
/**
* adds a component
*/
exports.addComponent = function (moduleName, module) {
exports.addComponent = (moduleName, module) => {
// save the component
components[moduleName] = module;
@ -48,14 +46,14 @@ exports.addComponent = function (moduleName, module) {
/**
* sets the socket.io and adds event functions for routing
*/
exports.setSocketIO = function (_socket) {
exports.setSocketIO = (_socket) => {
// save this socket internaly
socket = _socket;
socket.sockets.on('connection', (client) => {
// wrap the original send function to log the messages
client._send = client.send;
client.send = function (message) {
client.send = (message) => {
messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`);
client._send(message);
};
@ -66,7 +64,7 @@ exports.setSocketIO = function (_socket) {
}
client.on('message', async (message) => {
if (message.protocolVersion && message.protocolVersion != 2) {
if (message.protocolVersion && message.protocolVersion !== 2) {
messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`);
return;
}

View file

@ -1,8 +1,9 @@
const eejs = require('ep_etherpad-lite/node/eejs');
'use strict';
const eejs = require('../../eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
args.app.get('/admin', (req, res) => {
if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/');
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
});
return cb();

View file

@ -4,7 +4,6 @@ const eejs = require('../../eejs');
const settings = require('../../utils/Settings');
const installer = require('../../../static/js/pluginfw/installer');
const plugins = require('../../../static/js/pluginfw/plugin_defs');
const _ = require('underscore');
const semver = require('semver');
const UpdateCheck = require('../../utils/UpdateCheck');
@ -51,7 +50,7 @@ exports.socketio = (hookName, args, cb) => {
try {
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
const updatable = _(plugins.plugins).keys().filter((plugin) => {
const updatable = Object.keys(plugins.plugins).filter((plugin) => {
if (!results[plugin]) return false;
const latestVersion = results[plugin].version;

View file

@ -1,9 +1,11 @@
'use strict';
const log4js = require('log4js');
const clientLogger = log4js.getLogger('client');
const formidable = require('formidable');
const apiHandler = require('../../handler/APIHandler');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// The Etherpad client side sends information about how a disconnect happened
args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
new formidable.IncomingForm().parse(req, (err, fields, files) => {
@ -15,8 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// The Etherpad client side sends information about client side javscript errors
args.app.post('/jserror', (req, res) => {
new formidable.IncomingForm().parse(req, (err, fields, files) => {
let data;
try {
var data = JSON.parse(fields.errorInfo);
data = JSON.parse(fields.errorInfo);
} catch (e) {
return res.end();
}

View file

@ -1,6 +1,8 @@
const stats = require('ep_etherpad-lite/node/stats');
'use strict';
exports.expressCreateServer = function (hook_name, args, cb) {
const stats = require('../../stats');
exports.expressCreateServer = (hook_name, args, cb) => {
exports.app = args.app;
// Handle errors

View file

@ -1,39 +1,43 @@
const assert = require('assert').strict;
'use strict';
const hasPadAccess = require('../../padaccess');
const settings = require('../../utils/Settings');
const exportHandler = require('../../handler/ExportHandler');
const importHandler = require('../../handler/ImportHandler');
const padManager = require('../../db/PadManager');
const readOnlyManager = require('../../db/ReadOnlyManager');
const authorManager = require('../../db/AuthorManager');
const rateLimit = require('express-rate-limit');
const securityManager = require('../../db/SecurityManager');
const webaccess = require('./webaccess');
settings.importExportRateLimiting.onLimitReached = function (req, res, options) {
settings.importExportRateLimiting.onLimitReached = (req, res, options) => {
// when the rate limiter triggers, write a warning in the logs
console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
console.warn('Import/Export rate limiter triggered on ' +
`"${req.originalUrl}" for IP address ${req.ip}`);
};
const limiter = rateLimit(settings.importExportRateLimiting);
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter);
args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => {
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
// send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
if (types.indexOf(req.params.type) === -1) {
return next();
}
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() == 'no' &&
if (settings.exportAvailable() === 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured');
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature');
// ACHTUNG: do not include req.params.type in res.send() because there is
// no HTML escaping and it would lead to an XSS
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' +
' or soffice (LibreOffice) in settings.json to enable this feature');
return;
}

View file

@ -1,3 +1,5 @@
'use strict';
const RESERVED_WORDS = [
'abstract',
'arguments',
@ -65,9 +67,9 @@ const RESERVED_WORDS = [
'yield',
];
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/;
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|'.+'|\d+)\])*?$/;
module.exports.check = function (inputStr) {
module.exports.check = (inputStr) => {
let isValid = true;
inputStr.split('.').forEach((part) => {
if (!regex.test(part)) {

View file

@ -1,3 +1,5 @@
'use strict';
/**
* node/hooks/express/openapi.js
*
@ -31,7 +33,9 @@ const OPENAPI_VERSION = '3.0.2'; // Swagger/OAS version
const info = {
title: 'Etherpad API',
description:
'Etherpad is a real-time collaborative editor scalable to thousands of simultaneous real time users. It provides full data export capabilities, and runs on your server, under your control.',
'Etherpad is a real-time collaborative editor scalable to thousands of simultaneous ' +
'real time users. It provides full data export capabilities, and runs on your server, ' +
'under your control.',
termsOfService: 'https://etherpad.org/',
contact: {
name: 'The Etherpad Foundation',
@ -80,7 +84,9 @@ const resources = {
listSessions: {
operationId: 'listSessionsOfGroup',
summary: '',
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
responseSchema: {
sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}},
},
},
list: {
operationId: 'listAllGroups',
@ -109,7 +115,9 @@ const resources = {
listSessions: {
operationId: 'listSessionsOfAuthor',
summary: 'returns all sessions of an author',
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
responseSchema: {
sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}},
},
},
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
getName: {
@ -153,7 +161,8 @@ const resources = {
create: {
operationId: 'createPad',
description:
'creates a new (non-group) pad. Note that if you need to create a group Pad, you should call createGroupPad',
'creates a new (non-group) pad. Note that if you need to create a group Pad, ' +
'you should call createGroupPad',
},
getText: {
operationId: 'getText',
@ -607,7 +616,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
if (createHTTPError.isHttpError(err)) {
// pass http errors thrown by handler forward
throw err;
} else if (err.name == 'apierror') {
} else if (err.name === 'apierror') {
// parameters were wrong and the api stopped execution, pass the error
// convert to http error
throw new createHTTPError.BadRequest(err.message);

View file

@ -1,8 +1,10 @@
'use strict';
const readOnlyManager = require('../../db/ReadOnlyManager');
const hasPadAccess = require('../../padaccess');
const exporthtml = require('../../utils/ExportHtml');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// serve read only pad
args.app.get('/ro/:id', async (req, res) => {
// translate the read only pad to a padId

View file

@ -1,7 +1,9 @@
'use strict';
const padManager = require('../../db/PadManager');
const url = require('url');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', async (req, res, next, padId) => {
// ensure the padname is valid and the url doesn't end with a /
@ -17,12 +19,12 @@ exports.expressCreateServer = function (hook_name, args, cb) {
next();
} else {
// the pad id was sanitized, so we redirect to the sanitized version
let real_url = sanitizedPadId;
real_url = encodeURIComponent(real_url);
let realURL = sanitizedPadId;
realURL = encodeURIComponent(realURL);
const query = url.parse(req.url).query;
if (query) real_url += `?${query}`;
res.header('Location', real_url);
res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
if (query) realURL += `?${query}`;
res.header('Location', realURL);
res.status(302).send(`You should be redirected to <a href="${realURL}">${realURL}</a>`);
}
});
return cb();

View file

@ -1,14 +1,16 @@
'use strict';
const path = require('path');
const eejs = require('ep_etherpad-lite/node/eejs');
const toolbar = require('ep_etherpad-lite/node/utils/toolbar');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const eejs = require('../../eejs');
const toolbar = require('../../utils/toolbar');
const hooks = require('../../../static/js/pluginfw/hooks');
const settings = require('../../utils/Settings');
const webaccess = require('./webaccess');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// expose current stats
args.app.get('/stats', (req, res) => {
res.json(require('ep_etherpad-lite/node/stats').toJSON());
res.json(require('../../stats').toJSON());
});
// serve index.html under /
@ -24,7 +26,14 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// serve robots.txt
args.app.get('/robots.txt', (req, res) => {
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
let filePath = path.join(
settings.root,
'src',
'static',
'skins',
settings.skinName,
'robots.txt'
);
res.sendFile(filePath, (err) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) {
@ -66,7 +75,14 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// serve favicon.ico from all path levels except as a pad name
args.app.get(/\/favicon.ico$/, (req, res) => {
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico');
let filePath = path.join(
settings.root,
'src',
'static',
'skins',
settings.skinName,
'favicon.ico'
);
res.sendFile(filePath, (err) => {
// there is no custom favicon, send the default favicon

View file

@ -1,11 +1,12 @@
'use strict';
const minify = require('../../utils/Minify');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
const plugins = require('../../../static/js/pluginfw/plugin_defs');
const CachingMiddleware = require('../../utils/caching_middleware');
const settings = require('../../utils/Settings');
const Yajsml = require('etherpad-yajsml');
const _ = require('underscore');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
// Cache both minified and static.
const assetCache = new CachingMiddleware();
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
@ -34,7 +35,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
args.app.use(jsServer.handle.bind(jsServer));
// serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
// not very static, but served here so that client can do
// require("pluginfw/static/js/plugin-definitions.js");
args.app.get('/pluginfw/plugin-definitions.json', (req, res, next) => {
const clientParts = _(plugins.parts)
.filter((part) => _(part).has('client_hooks'));

View file

@ -1,9 +1,11 @@
'use strict';
const path = require('path');
const npm = require('npm');
const fs = require('fs');
const util = require('util');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.expressCreateServer = (hookName, args, cb) => {
args.app.get('/tests/frontend/specs_list.js', async (req, res) => {
const [coreTests, pluginTests] = await Promise.all([
exports.getCoreTests(),
@ -24,9 +26,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// path.join seems to normalize by default, but we'll just be explicit
const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/'));
const url2FilePath = function (url) {
const url2FilePath = (url) => {
let subPath = url.substr('/tests/frontend'.length);
if (subPath == '') {
if (subPath === '') {
subPath = 'index.html';
}
subPath = subPath.split('?')[0];
@ -49,8 +51,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`;
if(!specFilePath.endsWith('index.html')) res.setHeader('content-type', 'application/javascript');
if (!specFilePath.endsWith('index.html')) {
res.setHeader('content-type', 'application/javascript');
}
res.send(content);
});
});
@ -69,7 +72,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
const readdir = util.promisify(fs.readdir);
exports.getPluginTests = async function (callback) {
exports.getPluginTests = async (callback) => {
const moduleDir = 'node_modules/';
const specPath = '/static/tests/frontend/specs/';
const staticDir = '/static/plugins/';
@ -88,7 +91,4 @@ exports.getPluginTests = async function (callback) {
return Promise.all(promises).then(() => pluginSpecs);
};
exports.getCoreTests = function () {
// get the core test specs
return readdir('tests/frontend/specs');
};
exports.getCoreTests = () => readdir('tests/frontend/specs');

View file

@ -1,24 +1,23 @@
'use strict';
const languages = require('languages4translatewiki');
const fs = require('fs');
const path = require('path');
const _ = require('underscore');
const npm = require('npm');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins;
const semver = require('semver');
const plugins = require('../../static/js/pluginfw/plugin_defs.js').plugins;
const existsSync = require('../utils/path_exists');
const settings = require('../utils/Settings')
;
const settings = require('../utils/Settings');
// returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...}
function getAllLocales() {
const getAllLocales = () => {
const locales2paths = {};
// Puts the paths of all locale files contained in a given directory
// into `locales2paths` (files from various dirs are grouped by lang code)
// (only json files with valid language code as name)
function extractLangs(dir) {
const extractLangs = (dir) => {
if (!existsSync(dir)) return;
let stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
@ -31,12 +30,12 @@ function getAllLocales() {
const ext = path.extname(file);
const locale = path.basename(file, ext).toLowerCase();
if ((ext == '.json') && languages.isValid(locale)) {
if ((ext === '.json') && languages.isValid(locale)) {
if (!locales2paths[locale]) locales2paths[locale] = [];
locales2paths[locale].push(file);
}
});
}
};
// add core supported languages first
extractLangs(`${npm.root}/ep_etherpad-lite/locales`);
@ -78,29 +77,29 @@ function getAllLocales() {
}
return locales;
}
};
// returns a hash of all available languages availables with nativeName and direction
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
function getAvailableLangs(locales) {
const getAvailableLangs = (locales) => {
const result = {};
_.each(_.keys(locales), (langcode) => {
result[langcode] = languages.getLanguageInfo(langcode);
});
return result;
}
};
// returns locale index that will be served in /locales.json
const generateLocaleIndex = function (locales) {
const generateLocaleIndex = (locales) => {
const result = _.clone(locales); // keep English strings
_.each(_.keys(locales), (langcode) => {
if (langcode != 'en') result[langcode] = `locales/${langcode}.json`;
if (langcode !== 'en') result[langcode] = `locales/${langcode}.json`;
});
return JSON.stringify(result);
};
exports.expressCreateServer = function (n, args, cb) {
exports.expressCreateServer = (n, args, cb) => {
// regenerate locales on server restart
const locales = getAllLocales();
const localeIndex = generateLocaleIndex(locales);

View file

@ -1,3 +1,4 @@
'use strict';
const securityManager = require('./db/SecurityManager');
// checks for padAccess

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Controls the communication with the Abiword application
*/
@ -25,11 +26,12 @@ const os = require('os');
let doConvertTask;
// on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
// on windows we have to spawn a process for each convertion,
// cause the plugin abicommand doesn't exist on this platform
if (os.type().indexOf('Windows') > -1) {
let stdoutBuffer = '';
doConvertTask = function (task, callback) {
doConvertTask = (task, callback) => {
// span an abiword process to perform the conversion
const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]);
@ -46,11 +48,11 @@ if (os.type().indexOf('Windows') > -1) {
// throw exceptions if abiword is dieing
abiword.on('exit', (code) => {
if (code != 0) {
if (code !== 0) {
return callback(`Abiword died with exit code ${code}`);
}
if (stdoutBuffer != '') {
if (stdoutBuffer !== '') {
console.log(stdoutBuffer);
}
@ -58,17 +60,17 @@ if (os.type().indexOf('Windows') > -1) {
});
};
exports.convertFile = function (srcFile, destFile, type, callback) {
exports.convertFile = (srcFile, destFile, type, callback) => {
doConvertTask({srcFile, destFile, type}, callback);
};
}
// on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
// on unix operating systems, we can start abiword with abicommand and
// communicate with it via stdin/stdout
// thats much faster, about factor 10
else {
} else {
// spawn the abiword process
let abiword;
let stdoutCallback = null;
var spawnAbiword = function () {
const spawnAbiword = () => {
abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
let stdoutBuffer = '';
let firstPrompt = true;
@ -90,9 +92,9 @@ else {
stdoutBuffer += data.toString();
// we're searching for the prompt, cause this means everything we need is in the buffer
if (stdoutBuffer.search('AbiWord:>') != -1) {
if (stdoutBuffer.search('AbiWord:>') !== -1) {
// filter the feedback message
const err = stdoutBuffer.search('OK') != -1 ? null : stdoutBuffer;
const err = stdoutBuffer.search('OK') !== -1 ? null : stdoutBuffer;
// reset the buffer
stdoutBuffer = '';
@ -110,10 +112,10 @@ else {
};
spawnAbiword();
doConvertTask = function (task, callback) {
doConvertTask = (task, callback) => {
abiword.stdin.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`);
// create a callback that calls the task callback and the caller callback
stdoutCallback = function (err) {
stdoutCallback = (err) => {
callback();
console.log('queue continue');
try {
@ -126,7 +128,7 @@ else {
// Queue with the converts we have to do
const queue = async.queue(doConvertTask, 1);
exports.convertFile = function (srcFile, destFile, type, callback) {
exports.convertFile = (srcFile, destFile, type, callback) => {
queue.push({srcFile, destFile, type, callback});
};
}

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Library for deterministic relative filename expansion for Etherpad.
*/
@ -40,7 +41,7 @@ let etherpadRoot = null;
* @return {string[]|boolean} The shortened array, or false if there was no
* overlap.
*/
const popIfEndsWith = function (stringArray, lastDesiredElements) {
const popIfEndsWith = (stringArray, lastDesiredElements) => {
if (stringArray.length <= lastDesiredElements.length) {
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1} elements`);
@ -72,8 +73,8 @@ const popIfEndsWith = function (stringArray, lastDesiredElements) {
* @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application.
*/
exports.findEtherpadRoot = function () {
if (etherpadRoot !== null) {
exports.findEtherpadRoot = () => {
if (etherpadRoot != null) {
return etherpadRoot;
}
@ -126,7 +127,7 @@ exports.findEtherpadRoot = function () {
* it is returned unchanged. Otherwise it is interpreted
* relative to exports.root.
*/
exports.makeAbsolute = function (somePath) {
exports.makeAbsolute = (somePath) => {
if (path.isAbsolute(somePath)) {
return somePath;
}
@ -145,7 +146,7 @@ exports.makeAbsolute = function (somePath) {
* a subdirectory of the base one
* @return {boolean}
*/
exports.isSubdir = function (parent, arbitraryDir) {
exports.isSubdir = (parent, arbitraryDir) => {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir);
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The CLI module handles command line parameters
*/
@ -30,22 +31,22 @@ for (let i = 0; i < argv.length; i++) {
arg = argv[i];
// Override location of settings.json file
if (prevArg == '--settings' || prevArg == '-s') {
if (prevArg === '--settings' || prevArg === '-s') {
exports.argv.settings = arg;
}
// Override location of credentials.json file
if (prevArg == '--credentials') {
if (prevArg === '--credentials') {
exports.argv.credentials = arg;
}
// Override location of settings.json file
if (prevArg == '--sessionkey') {
if (prevArg === '--sessionkey') {
exports.argv.sessionkey = arg;
}
// Override location of settings.json file
if (prevArg == '--apikey') {
if (prevArg === '--apikey') {
exports.argv.apikey = arg;
}

View file

@ -1,3 +1,4 @@
'use strict';
/**
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
*
@ -16,9 +17,9 @@
const db = require('../db/DB');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const hooks = require('../../static/js/pluginfw/hooks');
exports.getPadRaw = async function (padId) {
exports.getPadRaw = async (padId) => {
const padKey = `pad:${padId}`;
const padcontent = await db.get(padKey);

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Helpers for export requests
*/
@ -18,9 +19,9 @@
* limitations under the License.
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const Changeset = require('../../static/js/Changeset');
exports.getPadPlainText = function (pad, revNum) {
exports.getPadPlainText = (pad, revNum) => {
const _analyzeLine = exports._analyzeLine;
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n');
@ -43,7 +44,7 @@ exports.getPadPlainText = function (pad, revNum) {
};
exports._analyzeLine = function (text, aline, apool) {
exports._analyzeLine = (text, aline, apool) => {
const line = {};
// identify list
@ -81,6 +82,5 @@ exports._analyzeLine = function (text, aline, apool) {
};
exports._encodeWhitespace = function (s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
};
exports._encodeWhitespace =
(s) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Copyright 2009 Google Inc.
*
@ -14,32 +15,29 @@
* limitations under the License.
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const Changeset = require('../../static/js/Changeset');
const padManager = require('../db/PadManager');
const _ = require('underscore');
const Security = require('ep_etherpad-lite/static/js/security');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const eejs = require('ep_etherpad-lite/node/eejs');
const Security = require('../../static/js/security');
const hooks = require('../../static/js/pluginfw/hooks');
const eejs = require('../eejs');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
const padutils = require('../../static/js/pad_utils').padutils;
async function getPadHTML(pad, revNum) {
const getPadHTML = async (pad, revNum) => {
let atext = pad.atext;
// fetch revision atext
if (revNum != undefined) {
if (revNum !== undefined) {
atext = await pad.getInternalRevisionAText(revNum);
}
// convert atext to html
return await getHTMLFromAtext(pad, atext);
}
};
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;
async function getHTMLFromAtext(pad, atext, authorColors) {
const getHTMLFromAtext = async (pad, atext, authorColors) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@ -72,9 +70,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
const anumMap = {};
let css = '';
const stripDotFromAuthorID = function (id) {
return id.replace(/\./g, '_');
};
const stripDotFromAuthorID = (id) => id.replace(/\./g, '_');
if (authorColors) {
css += '<style>\n';
@ -85,15 +81,14 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// skip non author attributes
if (attr[0] === 'author' && attr[1] !== '') {
// add to props array
var propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName);
const propName = `author${stripDotFromAuthorID(attr[1])}`;
const newLength = props.push(propName);
anumMap[a] = newLength - 1;
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if (attr[0] === 'removed') {
var propName = 'removed';
var newLength = props.push(propName);
const propName = 'removed';
const newLength = props.push(propName);
anumMap[a] = newLength - 1;
css += '.removed {text-decoration: line-through; ' +
@ -122,7 +117,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
});
function getLineHTML(text, attribs) {
const getLineHTML = (text, attribs) => {
// Use order of tags (b/i/u) as order of nesting, for simplicity
// and decent nesting. For example,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
@ -132,7 +127,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
const assem = Changeset.stringAssembler();
const openTags = [];
function getSpanClassFor(i) {
const getSpanClassFor = (i) => {
// return if author colors are disabled
if (!authorColors) return false;
@ -153,16 +148,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
return false;
}
};
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes
function isSpanWithData(i) {
const isSpanWithData = (i) => {
const property = props[i];
return _.isArray(property);
}
};
function emitOpenTag(i) {
const emitOpenTag = (i) => {
openTags.unshift(i);
const spanClass = getSpanClassFor(i);
@ -175,10 +170,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]);
assem.append('>');
}
}
};
// this closes an open tag and removes its reference from openTags
function emitCloseTag(i) {
const emitCloseTag = (i) => {
openTags.shift();
const spanClass = getSpanClassFor(i);
const spanWithData = isSpanWithData(i);
@ -190,13 +185,13 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
assem.append(tags[i]);
assem.append('>');
}
}
};
const urls = padutils.findURLs(text);
let idx = 0;
function processNextChars(numChars) {
const processNextChars = (numChars) => {
if (numChars <= 0) {
return;
}
@ -208,7 +203,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// based on the attribs used
while (iter.hasNext()) {
const o = iter.next();
var usedAttribs = [];
const usedAttribs = [];
// mark all attribs as used
Changeset.eachAttribNumber(o.attribs, (a) => {
@ -218,7 +213,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
});
let outermostTag = -1;
// find the outer most open tag that is no longer used
for (var i = openTags.length - 1; i >= 0; i--) {
for (let i = openTags.length - 1; i >= 0; i--) {
if (usedAttribs.indexOf(openTags[i]) === -1) {
outermostTag = i;
break;
@ -234,7 +229,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
// open all tags that are used but not open
for (i = 0; i < usedAttribs.length; i++) {
for (let i = 0; i < usedAttribs.length; i++) {
if (openTags.indexOf(usedAttribs[i]) === -1) {
emitOpenTag(usedAttribs[i]);
}
@ -258,14 +253,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
while (openTags.length > 0) {
emitCloseTag(openTags[0]);
}
} // end processNextChars
};
// end processNextChars
if (urls) {
urls.forEach((urlData) => {
const startIndex = urlData[0];
const url = urlData[1];
const urlLength = url.length;
processNextChars(startIndex - idx);
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document.
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML
// when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
@ -280,7 +277,8 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
processNextChars(text.length - idx);
return _processSpaces(assem.toString());
} // end getLineHTML
};
// end getLineHTML
const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can
@ -292,11 +290,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// => keeps track of the parents level of indentation
let openLists = [];
for (let i = 0; i < textLines.length; i++) {
var context;
var line = _analyzeLine(textLines[i], attribLines[i], apool);
let context;
const line = _analyzeLine(textLines[i], attribLines[i], apool);
const lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel)// If we are inside a list
{
// If we are inside a list
if (line.listLevel) {
context = {
line,
lineContent,
@ -315,8 +313,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
await hooks.aCallAll('getLineHTMLForExport', context);
// To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName));
if ((!prevLine || prevLine.listLevel !== line.listLevel) ||
(prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (
item.level === line.listLevel && item.type === line.listTypeName)
);
if (!exists) {
let prevLevel = 0;
if (prevLine && prevLine.listLevel) {
@ -326,23 +327,33 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
prevLevel = 0;
}
for (var diff = prevLevel; diff < line.listLevel; diff++) {
for (let diff = prevLevel; diff < line.listLevel; diff++) {
openLists.push({level: diff, type: line.listTypeName});
const prevPiece = pieces[pieces.length - 1];
if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) {
if (prevPiece.indexOf('<ul') === 0 ||
prevPiece.indexOf('<ol') === 0 ||
prevPiece.indexOf('</li>') === 0) {
/*
uncommenting this breaks nested ols..
if the previous item is NOT a ul, NOT an ol OR closing li then close the list
so we consider this HTML, I inserted ** where it throws a problem in Example Wrong..
<ol><li>one</li><li><ol><li>1.1</li><li><ol><li>1.1.1</li></ol></li></ol></li><li>two</li></ol>
so we consider this HTML,
I inserted ** where it throws a problem in Example Wrong..
<ol><li>one</li><li><ol><li>1.1</li><li><ol><li>1.1.1</li></ol></li></ol>
</li><li>two</li></ol>
Note that closing the li then re-opening for another li item here is wrong. The correct markup is
Note that closing the li then re-opening for another li item here is wrong.
The correct markup is
<ol><li>one<ol><li>1.1<ol><li>1.1.1</li></ol></li></ol></li><li>two</li></ol>
Exmaple Right: <ol class="number"><li>one</li><ol start="2" class="number"><li>1.1</li><ol start="3" class="number"><li>1.1.1</li></ol></li></ol><li>two</li></ol>
Example Wrong: <ol class="number"><li>one</li>**</li>**<ol start="2" class="number"><li>1.1</li>**</li>**<ol start="3" class="number"><li>1.1.1</li></ol></li></ol><li>two</li></ol>
So it's firing wrong where the current piece is an li and the previous piece is an ol and next piece is an ol
Exmaple Right: <ol class="number"><li>one</li><ol start="2" class="number">
<li>1.1</li><ol start="3" class="number"><li>1.1.1</li></ol></li></ol>
<li>two</li></ol>
Example Wrong: <ol class="number"><li>one</li>**</li>**
<ol start="2" class="number"><li>1.1</li>**</li>**<ol start="3" class="number">
<li>1.1.1</li></ol></li></ol><li>two</li></ol>
So it's firing wrong where the current piece is an li and the previous piece is
an ol and next piece is an ol
So to remedy this we can say if next piece is NOT an OL or UL.
// pieces.push("</li>");
@ -350,14 +361,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open
// don't do anything because the next item is a nested ol openener so
// we need to keep the li open
} else {
pieces.push('<li>');
}
}
if (line.listTypeName === 'number') {
// We introduce line.start here, this is useful for continuing Ordered list line numbers
// We introduce line.start here, this is useful for continuing
// Ordered list line numbers
// in case you have a bullet in a list IE you Want
// 1. hello
// * foo
@ -384,18 +397,24 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
// To close list elements
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) {
if (nextLine &&
nextLine.listLevel === line.listLevel &&
line.listTypeName === nextLine.listTypeName) {
if (context.lineContent) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open
// don't do anything because the next item is a nested ol openener so we need to
// keep the li open
} else {
pieces.push('</li>');
}
}
}
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) {
if ((!nextLine ||
!nextLine.listLevel ||
nextLine.listLevel < line.listLevel) ||
(nextLine && line.listTypeName !== nextLine.listTypeName)) {
let nextLevel = 0;
if (nextLine && nextLine.listLevel) {
nextLevel = nextLine.listLevel;
@ -404,10 +423,11 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
nextLevel = 0;
}
for (var diff = nextLevel; diff < line.listLevel; diff++) {
for (let diff = nextLevel; diff < line.listLevel; diff++) {
openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) {
if (pieces[pieces.length - 1].indexOf('</ul') === 0 ||
pieces[pieces.length - 1].indexOf('</ol') === 0) {
pieces.push('</li>');
}
@ -418,8 +438,8 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
}
}
} else// outside any list, need to close line.listLevel of lists
{
} else {
// outside any list, need to close line.listLevel of lists
context = {
line,
lineContent,
@ -435,9 +455,9 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
return pieces.join('');
}
};
exports.getPadHTMLDocument = async function (padId, revNum) {
exports.getPadHTMLDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export
@ -461,7 +481,7 @@ exports.getPadHTMLDocument = async function (padId, revNum) {
};
// copied from ACE
function _processSpaces(s) {
const _processSpaces = (s) => {
const doesWrap = true;
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
@ -476,34 +496,37 @@ function _processSpaces(s) {
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--) {
var p = parts[i];
if (p == ' ') {
for (let i = parts.length - 1; i >= 0; i--) {
const p = parts[i];
if (p === ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
} else if (p.charAt(0) != '<') {
} else if (p.charAt(0) !== '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == ' ') {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
break;
} else if (p.charAt(0) != '<') {
} else if (p.charAt(0) !== '<') {
break;
}
}
} else {
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == ' ') {
for (let i = 0; i < parts.length; i++) {
const p = parts[i];
if (p === ' ') {
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
}
};
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;

View file

@ -1,3 +1,4 @@
'use strict';
/**
* TXT export
*/
@ -18,15 +19,15 @@
* limitations under the License.
*/
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const Changeset = require('../../static/js/Changeset');
const padManager = require('../db/PadManager');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
const getPadTXT = async function (pad, revNum) {
const getPadTXT = async (pad, revNum) => {
let atext = pad.atext;
if (revNum != undefined) {
if (revNum !== undefined) {
// fetch revision atext
atext = await pad.getInternalRevisionAText(revNum);
}
@ -37,7 +38,7 @@ const getPadTXT = async function (pad, revNum) {
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
function getTXTFromAtext(pad, atext, authorColors) {
const getTXTFromAtext = (pad, atext, authorColors) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@ -53,7 +54,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
});
function getLineTXT(text, attribs) {
const getLineTXT = (text, attribs) => {
const propVals = [false, false, false];
const ENTER = 1;
const STAY = 2;
@ -69,7 +70,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
let idx = 0;
function processNextChars(numChars) {
const processNextChars = (numChars) => {
if (numChars <= 0) {
return;
}
@ -79,7 +80,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
while (iter.hasNext()) {
const o = iter.next();
var propChanged = false;
let propChanged = false;
Changeset.eachAttribNumber(o.attribs, (a) => {
if (a in anumMap) {
@ -94,7 +95,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
});
for (var i = 0; i < propVals.length; i++) {
for (let i = 0; i < propVals.length; i++) {
if (propVals[i] === true) {
propVals[i] = LEAVE;
propChanged = true;
@ -110,7 +111,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
// leaving bold (e.g.) also leaves italics, etc.
let left = false;
for (var i = 0; i < propVals.length; i++) {
for (let i = 0; i < propVals.length; i++) {
const v = propVals[i];
if (!left) {
@ -123,9 +124,9 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
}
var tags2close = [];
const tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--) {
for (let i = propVals.length - 1; i >= 0; i--) {
if (propVals[i] === LEAVE) {
// emitCloseTag(i);
tags2close.push(i);
@ -136,7 +137,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
}
for (var i = 0; i < propVals.length; i++) {
for (let i = 0; i < propVals.length; i++) {
if (propVals[i] === ENTER || propVals[i] === STAY) {
propVals[i] = true;
}
@ -163,18 +164,20 @@ function getTXTFromAtext(pad, atext, authorColors) {
assem.append(s);
} // end iteration over spans in line
var tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--) {
const tags2close = [];
for (let i = propVals.length - 1; i >= 0; i--) {
if (propVals[i]) {
tags2close.push(i);
propVals[i] = false;
}
}
} // end processNextChars
};
// end processNextChars
processNextChars(text.length - idx);
return (assem.toString());
} // end getLineHTML
};
// end getLineHTML
const pieces = [css];
@ -193,7 +196,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
let lineContent = getLineTXT(line.text, line.aline);
if (line.listTypeName == 'bullet') {
if (line.listTypeName === 'bullet') {
lineContent = `* ${lineContent}`; // add a bullet
}
@ -212,7 +215,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
}
if (line.listTypeName == 'number') {
if (line.listTypeName === 'number') {
/*
* listLevel == amount of indentation
* listNumber(s) == item number
@ -249,11 +252,11 @@ function getTXTFromAtext(pad, atext, authorColors) {
}
return pieces.join('');
}
};
exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = async function (padId, revNum) {
exports.getPadTXTDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum);
};

View file

@ -1,3 +1,5 @@
// 'use strict';
// Uncommenting above breaks tests.
/**
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
*
@ -14,12 +16,11 @@
* limitations under the License.
*/
const log4js = require('log4js');
const db = require('../db/DB');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const hooks = require('../../static/js/pluginfw/hooks');
exports.setPadRaw = function (padId, records) {
records = JSON.parse(records);
exports.setPadRaw = (padId, r) => {
const records = JSON.parse(r);
Object.keys(records).forEach(async (key) => {
let value = records[key];

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Copyright Yaco Sistemas S.L. 2011.
*
@ -15,8 +16,8 @@
*/
const log4js = require('log4js');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const contentcollector = require('ep_etherpad-lite/static/js/contentcollector');
const Changeset = require('../../static/js/Changeset');
const contentcollector = require('../../static/js/contentcollector');
const cheerio = require('cheerio');
const rehype = require('rehype');
const minifyWhitespace = require('rehype-minify-whitespace');
@ -69,7 +70,7 @@ exports.setPadHTML = async (pad, html) => {
apiLogger.debug(newText);
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
function eachAttribRun(attribs, func /* (startInNewText, endInNewText, attribs)*/) {
const eachAttribRun = (attribs, func /* (startInNewText, endInNewText, attribs)*/) => {
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = 0;
@ -82,7 +83,7 @@ exports.setPadHTML = async (pad, html) => {
}
textIndex = nextIndex;
}
}
};
// create a new changeset with a helper builder object
const builder = Changeset.builder(1);

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Controls the communication with LibreOffice
*/
@ -24,52 +25,9 @@ const path = require('path');
const settings = require('./Settings');
const spawn = require('child_process').spawn;
// Conversion tasks will be queued up, so we don't overload the system
const queue = async.queue(doConvertTask, 1);
const libreOfficeLogger = log4js.getLogger('LibreOffice');
/**
* Convert a file from one type to another
*
* @param {String} srcFile The path on disk to convert
* @param {String} destFile The path on disk where the converted file should be stored
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
exports.convertFile = function (srcFile, destFile, type, callback) {
// Used for the moving of the file, not the conversion
const fileExtension = type;
if (type === 'html') {
// "html:XHTML Writer File:UTF8" does a better job than normal html exports
if (path.extname(srcFile).toLowerCase() === '.doc') {
type = 'html';
}
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
if (path.extname(srcFile).toLowerCase() === '.pdf') {
type = 'html:XHTML Draw File';
}
}
// soffice can't convert from html to doc directly (verified with LO 5 and 6)
// we need to convert to odt first, then to doc
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') {
queue.push({
srcFile,
destFile: destFile.replace(/\.doc$/, '.odt'),
type: 'odt',
callback() {
queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension});
},
});
} else {
queue.push({srcFile, destFile, type, callback, fileExtension});
}
};
function doConvertTask(task, callback) {
const doConvertTask = (task, callback) => {
const tmpDir = os.tmpdir();
async.series([
@ -77,8 +35,10 @@ function doConvertTask(task, callback) {
* use LibreOffice to convert task.srcFile to another format, given in
* task.type
*/
function (callback) {
libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
(callback) => {
libreOfficeLogger.debug(
`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`
);
const soffice = spawn(settings.soffice, [
'--headless',
'--invisible',
@ -112,7 +72,7 @@ function doConvertTask(task, callback) {
soffice.on('exit', (code) => {
clearTimeout(hangTimeout);
if (code != 0) {
if (code !== 0) {
// Throw an exception if libreoffice failed
return callback(`LibreOffice died with exit code ${code} and message: ${stdoutBuffer}`);
}
@ -123,10 +83,10 @@ function doConvertTask(task, callback) {
},
// Move the converted file to the correct place
function (callback) {
(callback) => {
const filename = path.basename(task.srcFile);
const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
const sourcePath = path.join(tmpDir, sourceFilename);
const sourceFile = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
const sourcePath = path.join(tmpDir, sourceFile);
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
fs.rename(sourcePath, task.destFile, callback);
},
@ -137,4 +97,55 @@ function doConvertTask(task, callback) {
// Invoke the callback for the task
task.callback(err);
});
};
// Conversion tasks will be queued up, so we don't overload the system
const queue = async.queue(doConvertTask, 1);
/**
* Convert a file from one type to another
*
* @param {String} srcFile The path on disk to convert
* @param {String} destFile The path on disk where the converted file should be stored
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
exports.convertFile = (srcFile, destFile, type, callback) => {
// Used for the moving of the file, not the conversion
const fileExtension = type;
if (type === 'html') {
// "html:XHTML Writer File:UTF8" does a better job than normal html exports
if (path.extname(srcFile).toLowerCase() === '.doc') {
type = 'html';
}
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
if (path.extname(srcFile).toLowerCase() === '.pdf') {
type = 'html:XHTML Draw File';
}
}
// soffice can't convert from html to doc directly (verified with LO 5 and 6)
// we need to convert to odt first, then to doc
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') {
queue.push({
srcFile,
destFile: destFile.replace(/\.doc$/, '.odt'),
type: 'odt',
callback: () => {
queue.push(
{
srcFile: srcFile.replace(/\.html$/, '.odt'),
destFile,
type,
callback,
fileExtension,
}
);
},
});
} else {
queue.push({srcFile, destFile, type, callback, fileExtension});
}
};

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Worker thread to minify JS & CSS files out of the main NodeJS thread
*/
@ -7,12 +8,9 @@ const Terser = require('terser');
const path = require('path');
const Threads = require('threads');
function compressJS(content) {
return Terser.minify(content);
}
const compressJS = (content) => Terser.minify(content);
function compressCSS(filename, ROOT_DIR) {
return new Promise((res, rej) => {
const compressCSS = (filename, ROOT_DIR) => new Promise((res, rej) => {
try {
const absPath = path.join(ROOT_DIR, filename);
@ -57,7 +55,6 @@ function compressCSS(filename, ROOT_DIR) {
callback(null, content);
}
});
}
Threads.expose({
compressJS,

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Checks related to Node runtime version
*/
@ -25,7 +26,7 @@ const semver = require('semver');
*
* @param {String} minNodeVersion Minimum required Node version
*/
exports.enforceMinNodeVersion = function (minNodeVersion) {
exports.enforceMinNodeVersion = (minNodeVersion) => {
const currentNodeVersion = process.version;
// we cannot use template literals, since we still do not know if we are
@ -41,10 +42,12 @@ exports.enforceMinNodeVersion = function (minNodeVersion) {
/**
* Prints a warning if running on a supported but deprecated Node version
*
* @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases
* @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are
* deprecated
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated
* Node releases
*/
exports.checkDeprecationStatus = function (lowestNonDeprecatedNodeVersion, epRemovalVersion) {
exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion, epRemovalVersion) => {
const currentNodeVersion = process.version;
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {

View file

@ -1,3 +1,4 @@
'use strict';
/**
* The Settings module reads the settings out of settings.json and provides
* this information to the other modules
@ -31,7 +32,6 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const argv = require('./Cli').argv;
const npm = require('npm/lib/npm.js');
const jsonminify = require('jsonminify');
const log4js = require('log4js');
const randomString = require('./randomstring');
@ -381,29 +381,30 @@ exports.commitRateLimiting = {
exports.importMaxFileSize = 50 * 1024 * 1024;
// checks if abiword is avaiable
exports.abiwordAvailable = function () {
exports.abiwordAvailable = () => {
if (exports.abiword != null) {
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
exports.sofficeAvailable = function () {
exports.sofficeAvailable = () => {
if (exports.soffice != null) {
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
exports.exportAvailable = function () {
exports.exportAvailable = () => {
const abiword = exports.abiwordAvailable();
const soffice = exports.sofficeAvailable();
if (abiword == 'no' && soffice == 'no') {
if (abiword === 'no' && soffice === 'no') {
return 'no';
} else if ((abiword == 'withoutPDF' && soffice == 'no') || (abiword == 'no' && soffice == 'withoutPDF')) {
} else if ((abiword === 'withoutPDF' && soffice === 'no') ||
(abiword === 'no' && soffice === 'withoutPDF')) {
return 'withoutPDF';
} else {
return 'yes';
@ -411,7 +412,7 @@ exports.exportAvailable = function () {
};
// Provide git version if available
exports.getGitCommit = function () {
exports.getGitCommit = () => {
let version = '';
try {
let rootPath = exports.root;
@ -436,9 +437,7 @@ exports.getGitCommit = function () {
};
// Return etherpad version from package.json
exports.getEpVersion = function () {
return require('ep_etherpad-lite/package.json').version;
};
exports.getEpVersion = () => require('../../package.json').version;
/**
* Receives a settingsObj and, if the property name is a valid configuration
@ -447,7 +446,7 @@ exports.getEpVersion = function () {
* This code refactors a previous version that copied & pasted the same code for
* both "settings.json" and "credentials.json".
*/
function storeSettings(settingsObj) {
const storeSettings = (settingsObj) => {
for (const i in settingsObj) {
// test if the setting starts with a lowercase character
if (i.charAt(0).search('[a-z]') !== 0) {
@ -456,7 +455,7 @@ function storeSettings(settingsObj) {
// we know this setting, so we overwrite it
// or it's a settings hash, specific to a plugin
if (exports[i] !== undefined || i.indexOf('ep_') == 0) {
if (exports[i] !== undefined || i.indexOf('ep_') === 0) {
if (_.isObject(settingsObj[i]) && !_.isArray(settingsObj[i])) {
exports[i] = _.defaults(settingsObj[i], exports[i]);
} else {
@ -467,7 +466,7 @@ function storeSettings(settingsObj) {
console.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`);
}
}
}
};
/*
* If stringValue is a numeric string, or its value is "true" or "false", coerce
@ -481,7 +480,7 @@ function storeSettings(settingsObj) {
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
* in the literal string "null", instead.
*/
function coerceValue(stringValue) {
const coerceValue = (stringValue) => {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
@ -502,7 +501,7 @@ function coerceValue(stringValue) {
// otherwise, return this value as-is
return stringValue;
}
};
/**
* Takes a javascript object containing Etherpad's configuration, and returns
@ -540,7 +539,7 @@ function coerceValue(stringValue) {
*
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
*/
function lookupEnvironmentVariables(obj) {
const lookupEnvironmentVariables = (obj) => {
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
/*
* the first invocation of replacer() is with an empty key. Just go on, or
@ -569,7 +568,7 @@ function lookupEnvironmentVariables(obj) {
// MUXATOR 2019-03-21: we could use named capture groups here once we migrate to nodejs v10
const match = value.match(/^\$\{([^:]*)(:((.|\n)*))?\}$/);
if (match === null) {
if (match == null) {
// no match: use the value literally, without any substitution
return value;
@ -613,7 +612,7 @@ function lookupEnvironmentVariables(obj) {
const newSettings = JSON.parse(stringifiedAndReplaced);
return newSettings;
}
};
/**
* - reads the JSON configuration file settingsFilename from disk
@ -623,7 +622,7 @@ function lookupEnvironmentVariables(obj) {
*
* The isSettings variable only controls the error logging.
*/
function parseSettings(settingsFilename, isSettings) {
const parseSettings = (settingsFilename, isSettings) => {
let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction;
@ -663,9 +662,9 @@ function parseSettings(settingsFilename, isSettings) {
process.exit(1);
}
}
};
exports.reloadSettings = function reloadSettings() {
exports.reloadSettings = () => {
// Discover where the settings file lives
const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
@ -695,7 +694,7 @@ exports.reloadSettings = function reloadSettings() {
const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
const countPieces = exports.skinName.split(path.sep).length;
if (countPieces != 1) {
if (countPieces !== 1) {
console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "colibris".`);
exports.skinName = 'colibris';
@ -766,7 +765,7 @@ exports.reloadSettings = function reloadSettings() {
}
if (exports.dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is fine for testing but not recommended for production.';
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = `${exports.defaultPadText}\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
}

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Tidy up the HTML in a given file
*/
@ -6,7 +7,7 @@ const log4js = require('log4js');
const settings = require('./Settings');
const spawn = require('child_process').spawn;
exports.tidy = function (srcFile) {
exports.tidy = (srcFile) => {
const logger = log4js.getLogger('TidyHtml');
return new Promise((resolve, reject) => {

View file

@ -1,11 +1,11 @@
'use strict';
const semver = require('semver');
const settings = require('./Settings');
const request = require('request');
let infos;
function loadEtherpadInformations() {
return new Promise((resolve, reject) => {
const loadEtherpadInformations = () => new Promise((resolve, reject) => {
request('https://static.etherpad.org/info.json', (er, response, body) => {
if (er) return reject(er);
@ -17,14 +17,13 @@ function loadEtherpadInformations() {
}
});
});
}
exports.getLatestVersion = function () {
exports.getLatestVersion = () => {
exports.needsUpdate();
return infos.latestVersion;
};
exports.needsUpdate = function (cb) {
exports.needsUpdate = (cb) => {
loadEtherpadInformations().then((info) => {
if (semver.gt(info.latestVersion, settings.getEpVersion())) {
if (cb) return cb(true);
@ -35,7 +34,7 @@ exports.needsUpdate = function (cb) {
});
};
exports.check = function () {
exports.check = () => {
exports.needsUpdate((needsUpdate) => {
if (needsUpdate) {
console.warn(`Update available: Download the actual version ${infos.latestVersion}`);

View file

@ -1,3 +1,5 @@
'use strict';
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
@ -47,18 +49,16 @@ CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
const responseCache = {};
function djb2Hash(data) {
const djb2Hash = (data) => {
const chars = data.split('').map((str) => str.charCodeAt(0));
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
}
};
function generateCacheKeyWithSha256(path) {
return _crypto.createHash('sha256').update(path).digest('hex');
}
const generateCacheKeyWithSha256 =
(path) => _crypto.createHash('sha256').update(path).digest('hex');
function generateCacheKeyWithDjb2(path) {
return Buffer.from(djb2Hash(path)).toString('hex');
}
const generateCacheKeyWithDjb2 =
(path) => Buffer.from(djb2Hash(path)).toString('hex');
let generateCacheKey;
@ -66,7 +66,7 @@ if (_crypto) {
generateCacheKey = generateCacheKeyWithSha256;
} else {
generateCacheKey = generateCacheKeyWithDjb2;
console.warn('No crypto support in this nodejs runtime. A fallback to Djb2 (weaker) will be used.');
console.warn('No crypto support in this nodejs runtime. Djb2 (weaker) will be used.');
}
// MIMIC https://github.com/microsoft/TypeScript/commit/9677b0641cc5ba7d8b701b4f892ed7e54ceaee9a - END
@ -80,8 +80,8 @@ if (_crypto) {
function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
function handle(req, res, next) {
if (!(req.method == 'GET' || req.method == 'HEAD') || !CACHE_DIR) {
const handle = (req, res, next) => {
if (!(req.method === 'GET' || req.method === 'HEAD') || !CACHE_DIR) {
return next(undefined, req, res);
}
@ -89,7 +89,7 @@ CachingMiddleware.prototype = new function () {
const old_res = {};
const supportsGzip =
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
(req.get('Accept-Encoding') || '').indexOf('gzip') !== -1;
const path = require('url').parse(req.url).path;
const cacheKey = generateCacheKey(path);
@ -116,7 +116,7 @@ CachingMiddleware.prototype = new function () {
const _headers = {};
old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) {
res.setHeader = (key, value) => {
// Don't set cookies, see issue #707
if (key.toLowerCase() === 'set-cookie') return;
@ -126,11 +126,8 @@ CachingMiddleware.prototype = new function () {
old_res.writeHead = res.writeHead;
res.writeHead = function (status, headers) {
const lastModified = (res.getHeader('last-modified') &&
new Date(res.getHeader('last-modified')));
res.writeHead = old_res.writeHead;
if (status == 200) {
if (status === 200) {
// Update cache
let buffer = '';
@ -169,7 +166,7 @@ CachingMiddleware.prototype = new function () {
respond();
});
};
} else if (status == 304) {
} else if (status === 304) {
// Nothing new changed from the cached version.
old_res.write = res.write;
old_res.end = res.end;
@ -204,10 +201,10 @@ CachingMiddleware.prototype = new function () {
const lastModified = (headers['last-modified'] &&
new Date(headers['last-modified']));
if (statusCode == 200 && lastModified <= modifiedSince) {
if (statusCode === 200 && lastModified <= modifiedSince) {
res.writeHead(304, headers);
res.end();
} else if (req.method == 'GET') {
} else if (req.method === 'GET') {
const readStream = fs.createReadStream(pathStr);
res.writeHead(statusCode, headers);
readStream.pipe(res);
@ -217,7 +214,7 @@ CachingMiddleware.prototype = new function () {
}
}
});
}
};
this.handle = handle;
}();

View file

@ -1,3 +1,4 @@
'use strict';
/**
* CustomError
*

View file

@ -1,3 +1,4 @@
'use strict';
const Changeset = require('../../static/js/Changeset');
const exportHtml = require('./ExportHtml');
@ -125,7 +126,7 @@ PadDiff.prototype._addAuthors = function (authors) {
// add to array if not in the array
authors.forEach((author) => {
if (self._authors.indexOf(author) == -1) {
if (self._authors.indexOf(author) === -1) {
self._authors.push(author);
}
});
@ -138,7 +139,6 @@ PadDiff.prototype._createDiffAtext = async function () {
let atext = await this._createClearStartAtext(this._fromRev);
let superChangeset = null;
const rev = this._fromRev + 1;
for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) {
// get the bulk
@ -161,7 +161,7 @@ PadDiff.prototype._createDiffAtext = async function () {
addedAuthors.push(authors[i]);
// compose it with the superChangset
if (superChangeset === null) {
if (superChangeset == null) {
superChangeset = changeset;
} else {
superChangeset = Changeset.composeWithDeletions(superChangeset, changeset, this._pad.pool);
@ -172,7 +172,8 @@ PadDiff.prototype._createDiffAtext = async function () {
this._addAuthors(addedAuthors);
}
// if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
// if there are only clearAuthorship changesets, we don't get a superChangeset,
// so we can skip this step
if (superChangeset) {
const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
@ -205,7 +206,8 @@ PadDiff.prototype.getHtml = async function () {
};
PadDiff.prototype.getAuthors = async function () {
// check if html was already produced, if not produce it, this generates the author array at the same time
// check if html was already produced, if not produce it, this generates
// the author array at the same time
if (this._html == null) {
await this.getHtml();
}
@ -213,7 +215,7 @@ PadDiff.prototype.getAuthors = async function () {
return self._authors;
};
PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apool) {
PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
// unpack
const unpacked = Changeset.unpack(changeset);
@ -245,7 +247,8 @@ PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apoo
return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
};
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
// this method is 80% like Changeset.inverse. I just changed so instead of reverting,
// it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const lines = Changeset.splitTextLines(startAText.text);
const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
@ -254,21 +257,21 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
// They may be arrays or objects with .get(i) and .length methods.
// They include final newlines on lines.
function lines_get(idx) {
const linesGet = (idx) => {
if (lines.get) {
return lines.get(idx);
} else {
return lines[idx];
}
}
};
function alines_get(idx) {
const aLinesGet = (idx) => {
if (alines.get) {
return alines.get(idx);
} else {
return alines[idx];
}
}
};
let curLine = 0;
let curChar = 0;
@ -280,10 +283,10 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const csIter = Changeset.opIterator(unpacked.ops);
const builder = Changeset.builder(unpacked.newLen);
function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) {
if ((!curLineOpIter) || (curLineOpIterLine != curLine)) {
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
// create curLineOpIter and advance it to curChar
curLineOpIter = Changeset.opIterator(alines_get(curLine));
curLineOpIter = Changeset.opIterator(aLinesGet(curLine));
curLineOpIterLine = curLine;
let indexIntoLine = 0;
let done = false;
@ -304,7 +307,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curChar = 0;
curLineOpIterLine = curLine;
curLineNextOp.chars = 0;
curLineOpIter = Changeset.opIterator(alines_get(curLine));
curLineOpIter = Changeset.opIterator(aLinesGet(curLine));
}
if (!curLineNextOp.chars) {
@ -313,7 +316,8 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
func(charsToUse, curLineNextOp.attribs,
charsToUse === curLineNextOp.chars && curLineNextOp.lines > 0);
numChars -= charsToUse;
curLineNextOp.chars -= charsToUse;
curChar += charsToUse;
@ -323,64 +327,66 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curLine++;
curChar = 0;
}
}
};
function skip(N, L) {
const skip = (N, L) => {
if (L) {
curLine += L;
curChar = 0;
} else if (curLineOpIter && curLineOpIterLine == curLine) {
} else if (curLineOpIter && curLineOpIterLine === curLine) {
consumeAttribRuns(N, () => {});
} else {
curChar += N;
}
}
};
function nextText(numChars) {
const nextText = (numChars) => {
let len = 0;
const assem = Changeset.stringAssembler();
const firstString = lines_get(curLine).substring(curChar);
const firstString = linesGet(curLine).substring(curChar);
len += firstString.length;
assem.append(firstString);
let lineNum = curLine + 1;
while (len < numChars) {
const nextString = lines_get(lineNum);
const nextString = linesGet(lineNum);
len += nextString.length;
assem.append(nextString);
lineNum++;
}
return assem.toString().substring(0, numChars);
}
};
function cachedStrFunc(func) {
const cachedStrFunc = (func) => {
const cache = {};
return function (s) {
return (s) => {
if (!cache[s]) {
cache[s] = func(s);
}
return cache[s];
};
}
};
const attribKeys = [];
const attribValues = [];
// iterate over all operators of this changeset
while (csIter.hasNext()) {
var csOp = csIter.next();
const csOp = csIter.next();
if (csOp.opcode == '=') {
var textBank = nextText(csOp.chars);
if (csOp.opcode === '=') {
const textBank = nextText(csOp.chars);
// decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set.
// If the text this operator applies to is only a star, than this is a false positive and should be ignored
if (csOp.attribs && textBank != '*') {
// decide if this equal operator is an attribution change or not.
// We can see this by checkinf if attribs is set.
// If the text this operator applies to is only a star,
// than this is a false positive and should be ignored
if (csOp.attribs && textBank !== '*') {
const deletedAttrib = apool.putAttrib(['removed', true]);
var authorAttrib = apool.putAttrib(['author', '']);
let authorAttrib = apool.putAttrib(['author', '']);
attribKeys.length = 0;
attribValues.length = 0;
@ -393,14 +399,14 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
}
});
var undoBackToAttribs = cachedStrFunc((attribs) => {
const undoBackToAttribs = cachedStrFunc((attribs) => {
const backAttribs = [];
for (let i = 0; i < attribKeys.length; i++) {
const appliedKey = attribKeys[i];
const appliedValue = attribValues[i];
const oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
if (appliedValue != oldValue) {
if (appliedValue !== oldValue) {
backAttribs.push([appliedKey, oldValue]);
}
}
@ -408,7 +414,8 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
return Changeset.makeAttribsString('=', backAttribs, apool);
});
var oldAttribsAddition = `*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
const oldAttribsAddition =
`*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
let textLeftToProcess = textBank;
@ -427,7 +434,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
}
// get the text we want to procceed in this step
var processText = textLeftToProcess.substr(0, lengthToProcess);
const processText = textLeftToProcess.substr(0, lengthToProcess);
textLeftToProcess = textLeftToProcess.substr(lengthToProcess);
@ -437,13 +444,14 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
// consume the attributes of this linebreak
consumeAttribRuns(1, () => {});
} else {
// add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
var textBankIndex = 0;
// add the old text via an insert, but add a deletion attribute +
// the author attribute of the author who deleted it
let textBankIndex = 0;
consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => {
// get the old attributes back
var attribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
const oldAttribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
builder.insert(processText.substr(textBankIndex, len), attribs);
builder.insert(processText.substr(textBankIndex, len), oldAttribs);
textBankIndex += len;
});
@ -454,11 +462,11 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
skip(csOp.chars, csOp.lines);
builder.keep(csOp.chars, csOp.lines);
}
} else if (csOp.opcode == '+') {
} else if (csOp.opcode === '+') {
builder.keep(csOp.chars, csOp.lines);
} else if (csOp.opcode == '-') {
var textBank = nextText(csOp.chars);
var textBankIndex = 0;
} else if (csOp.opcode === '-') {
const textBank = nextText(csOp.chars);
let textBankIndex = 0;
consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);

View file

@ -1,6 +1,7 @@
'use strict';
const fs = require('fs');
const check = function (path) {
const check = (path) => {
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
let result;

View file

@ -1,3 +1,4 @@
'use strict';
/**
* Helpers to manipulate promises (like async but for promises).
*/

View file

@ -1,10 +1,10 @@
'use strict';
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
* Generates a random String with the given length. Is needed to generate the
* Author, Group, readonly, session Ids
*/
const crypto = require('crypto');
const randomString = function (len) {
return crypto.randomBytes(len).toString('hex');
};
const randomString = (len) => crypto.randomBytes(len).toString('hex');
module.exports = randomString;

View file

@ -19,7 +19,6 @@
, "gritter.js"
, "$js-cookie/src/js.cookie.js"
, "$tinycon/tinycon.js"
, "excanvas.js"
, "farbtastic.js"
, "skin_variants.js"
, "socketio.js"

View file

@ -1,16 +1,10 @@
'use strict';
/**
* The Toolbar Module creates and renders the toolbars and buttons
*/
const _ = require('underscore');
let tagAttributes;
let tag;
let Button;
let ButtonsGroup;
let Separator;
let defaultButtonAttributes;
let removeItem;
removeItem = function (array, what) {
const removeItem = (array, what) => {
let ax;
while ((ax = array.indexOf(what)) !== -1) {
array.splice(ax, 1);
@ -18,15 +12,13 @@ removeItem = function (array, what) {
return array;
};
defaultButtonAttributes = function (name, overrides) {
return {
const defaultButtonAttributes = (name, overrides) => ({
command: name,
localizationId: `pad.toolbar.${name}.title`,
class: `buttonicon buttonicon-${name}`,
};
};
});
tag = function (name, attributes, contents) {
const tag = (name, attributes, contents) => {
const aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) {
@ -36,7 +28,7 @@ tag = function (name, attributes, contents) {
}
};
tagAttributes = function (attributes) {
const tagAttributes = (attributes) => {
attributes = _.reduce(attributes || {}, (o, val, name) => {
if (!_.isUndefined(val)) {
o[name] = val;
@ -47,7 +39,7 @@ tagAttributes = function (attributes) {
return ` ${_.map(attributes, (val, name) => `${name}="${_.escape(val)}"`).join(' ')}`;
};
ButtonsGroup = function () {
const ButtonsGroup = function () {
this.buttons = [];
};
@ -65,9 +57,9 @@ ButtonsGroup.prototype.addButton = function (button) {
};
ButtonsGroup.prototype.render = function () {
if (this.buttons && this.buttons.length == 1) {
if (this.buttons && this.buttons.length === 1) {
this.buttons[0].grouping = '';
} else {
} else if (this.buttons && this.buttons.length > 1) {
_.first(this.buttons).grouping = 'grouped-left';
_.last(this.buttons).grouping = 'grouped-right';
_.each(this.buttons.slice(1, -1), (btn) => {
@ -80,11 +72,11 @@ ButtonsGroup.prototype.render = function () {
}).join('\n');
};
Button = function (attributes) {
const Button = function (attributes) {
this.attributes = attributes;
};
Button.load = function (btnName) {
Button.load = (btnName) => {
const button = module.exports.availableButtons[btnName];
try {
if (button.constructor === Button || button.constructor === SelectButton) {
@ -108,14 +100,17 @@ _.extend(Button.prototype, {
};
return tag('li', liAttributes,
tag('a', {'class': this.grouping, 'data-l10n-id': this.attributes.localizationId},
tag('button', {'class': ` ${this.attributes.class}`, 'data-l10n-id': this.attributes.localizationId})
tag('button', {
'class': ` ${this.attributes.class}`,
'data-l10n-id': this.attributes.localizationId,
})
)
);
},
});
var SelectButton = function (attributes) {
const SelectButton = function (attributes) {
this.attributes = attributes;
this.options = [];
};
@ -155,7 +150,7 @@ _.extend(SelectButton.prototype, Button.prototype, {
},
});
Separator = function () {};
const Separator = function () {};
Separator.prototype.render = function () {
return tag('li', {class: 'separator'});
};
@ -235,15 +230,11 @@ module.exports = {
this.availableButtons[buttonName] = buttonInfo;
},
button(attributes) {
return new Button(attributes);
},
separator() {
return (new Separator()).render();
},
selectButton(attributes) {
return new SelectButton(attributes);
},
button: (attributes) => new Button(attributes),
separator: () => (new Separator()).render(),
selectButton: (attributes) => new SelectButton(attributes),
/*
* Valid values for whichMenu: 'left' | 'right' | 'timeslider-right'
@ -271,7 +262,8 @@ module.exports = {
* sufficient to visit a single read only pad to cause the disappearence
* of the star button from all the pads.
*/
if ((buttons[0].indexOf('savedrevision') === -1) && (whichMenu === 'right') && (page === 'pad')) {
if ((buttons[0].indexOf('savedrevision') === -1) &&
(whichMenu === 'right') && (page === 'pad')) {
buttons[0].push('savedrevision');
}
}

View file

@ -65,7 +65,7 @@
"security": "1.0.0",
"semver": "5.6.0",
"slide": "1.1.6",
"socket.io": "^2.3.0",
"socket.io": "^2.4.1",
"terser": "^4.7.0",
"threads": "^1.4.0",
"tiny-worker": "^2.3.0",
@ -102,7 +102,6 @@
"/static/js/admin/jquery.autosize.js",
"/static/js/admin/minify.json.js",
"/static/js/browser.js",
"/static/js/excanvas.js",
"/static/js/farbtastic.js",
"/static/js/gritter.js",
"/static/js/html10n.js",
@ -150,7 +149,7 @@
},
"scripts": {
"lint": "eslint .",
"test": "nyc mocha --timeout 30000 --recursive ../tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs --reporter mochawesome",
"test": "nyc mocha --timeout 120000 --recursive ../tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs --reporter mochawesome",
"test-container": "nyc mocha --timeout 5000 ../tests/container/specs/api --reporter mochawesome"
},
"version": "1.8.7",

View file

@ -1,35 +0,0 @@
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_||
b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])},
initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect();
break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]=
h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+
1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b;
var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};
i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+
0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b,
a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b,
a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length==
5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=",
this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src,
'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="',
!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius),
" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_),
z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+
o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap),
'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(),
this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a,
0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager=
M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})();

View file

@ -1,6 +1,8 @@
'use strict';
// Farbtastic 2.0 alpha
// Original can be found at:
// https://github.com/mattfarina/farbtastic/blob/71ca15f4a09c8e5a08a1b0d1cf37ef028adf22f0/src/farbtastic.js
// Licensed under the terms of the GNU General Public License v2.0:
// https://github.com/mattfarina/farbtastic/blob/71ca15f4a09c8e5a08a1b0d1cf37ef028adf22f0/LICENSE.txt
// edited by Sebastian Castro <sebastian.castro@protonmail.com> on 2020-04-06
(function ($) {
@ -84,16 +86,6 @@ $._farbtastic = function (container, options) {
}
/////////////////////////////////////////////////////
//excanvas-compatible building of canvases
fb._makeCanvas = function(className){
var c = document.createElement('canvas');
if (!c.getContext) { // excanvas hack
c = window.G_vmlCanvasManager.initElement(c);
c.getContext(); //this creates the excanvas children
}
$(c).addClass(className);
return c;
}
/**
* Initialize the color picker widget.
@ -109,15 +101,27 @@ $._farbtastic = function (container, options) {
.html(
'<div class="farbtastic" style="position: relative">' +
'<div class="farbtastic-solid"></div>' +
'<canvas class="farbtastic-mask"></canvas>' +
'<canvas class="farbtastic-overlay"></canvas>' +
'</div>'
)
.children('.farbtastic')
.append(fb._makeCanvas('farbtastic-mask'))
.append(fb._makeCanvas('farbtastic-overlay'))
.end()
.find('*').attr(dim).css(dim).end()
.find('div>*').css('position', 'absolute');
// IE Fix: Recreate canvas elements with doc.createElement and excanvas.
browser.msie && $('canvas', container).each(function () {
// Fetch info.
var attr = { 'class': $(this).attr('class'), style: this.getAttribute('style') },
e = document.createElement('canvas');
// Replace element.
$(this).before($(e).attr(attr)).remove();
// Init with explorerCanvas.
G_vmlCanvasManager && G_vmlCanvasManager.initElement(e);
// Set explorerCanvas elements dimensions and absolute positioning.
$(e).attr(dim).css(dim).css('position', 'absolute')
.find('*').attr(dim).css(dim);
});
// Determine layout
fb.radius = (options.width - options.wheelWidth) / 2 - 1;
fb.square = Math.floor((fb.radius - options.wheelWidth / 2) * 0.7) - 1;
@ -160,12 +164,12 @@ $._farbtastic = function (container, options) {
m.lineWidth = w / r;
m.scale(r, r);
// Each segment goes from angle1 to angle2.
for (let i = 0; i <= n; ++i) {
for (var i = 0; i <= n; ++i) {
var d2 = i / n,
angle2 = d2 * Math.PI * 2,
// Endpoints
x1 = Math.sin(angle1), y1 = -Math.cos(angle1);
const x2 = Math.sin(angle2), y2 = -Math.cos(angle2),
x2 = Math.sin(angle2), y2 = -Math.cos(angle2),
// Midpoint chosen so that the endpoints are tangent to the circle.
am = (angle1 + angle2) / 2,
tan = 1 / Math.cos((angle2 - angle1) / 2),
@ -173,6 +177,26 @@ $._farbtastic = function (container, options) {
// New color
color2 = fb.pack(fb.HSLToRGB([d2, 1, 0.5]));
if (i > 0) {
if (browser.msie) {
// IE's gradient calculations mess up the colors. Correct along the diagonals.
var corr = (1 + Math.min(Math.abs(Math.tan(angle1)), Math.abs(Math.tan(Math.PI / 2 - angle1)))) / n;
color1 = fb.pack(fb.HSLToRGB([d1 - 0.15 * corr, 1, 0.5]));
color2 = fb.pack(fb.HSLToRGB([d2 + 0.15 * corr, 1, 0.5]));
// Create gradient fill between the endpoints.
var grad = m.createLinearGradient(x1, y1, x2, y2);
grad.addColorStop(0, color1);
grad.addColorStop(1, color2);
m.fillStyle = grad;
// Draw quadratic curve segment as a fill.
var r1 = (r + w / 2) / r, r2 = (r - w / 2) / r; // inner/outer radius.
m.beginPath();
m.moveTo(x1 * r1, y1 * r1);
m.quadraticCurveTo(xm * r1, ym * r1, x2 * r1, y2 * r1);
m.lineTo(x2 * r2, y2 * r2);
m.quadraticCurveTo(xm * r2, ym * r2, x1 * r2, y1 * r2);
m.fill();
}
else {
// Create gradient fill between the endpoints.
var grad = m.createLinearGradient(x1, y1, x2, y2);
grad.addColorStop(0, color1);
@ -184,6 +208,7 @@ $._farbtastic = function (container, options) {
m.quadraticCurveTo(xm, ym, x2, y2);
m.stroke();
}
}
// Prevent seams where curves join.
angle1 = angle2 - nudge; color1 = color2; d1 = d2;
}
@ -226,7 +251,7 @@ $._farbtastic = function (container, options) {
var ctx = buffer.getContext('2d');
var frame = ctx.getImageData(0, 0, sz + 1, sz + 1);
let i = 0;
var i = 0;
calculateMask(sz, sz, function (x, y, c, a) {
frame.data[i++] = frame.data[i++] = frame.data[i++] = c * 255;
frame.data[i++] = a * 255;
@ -301,7 +326,7 @@ $._farbtastic = function (container, options) {
// Update the overlay canvas.
fb.ctxOverlay.clearRect(-fb.mid, -fb.mid, sz, sz);
for (let i in circles) {
for (i in circles) {
var c = circles[i];
fb.ctxOverlay.lineWidth = c.lw;
fb.ctxOverlay.strokeStyle = c.c;

View file

@ -28,7 +28,6 @@ let socket;
// assigns to the global `$` and augments it with plugins.
require('./jquery');
require('./farbtastic');
require('./excanvas');
require('./gritter');
const Cookies = require('./pad_utils').Cookies;

View file

@ -1,4 +1,4 @@
/* global __dirname, __filename, afterEach, before, beforeEach, describe, it, require */
'use strict';
/*
* Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints.
@ -6,11 +6,11 @@
const assert = require('assert').strict;
const common = require('../../common');
const superagent = require(`${__dirname}/../../../../src/node_modules/superagent`);
const superagent = require('ep_etherpad-lite/node_modules/superagent');
const fs = require('fs');
const settings = require(`${__dirname}/../../../../src/node/utils/Settings`);
const padManager = require(`${__dirname}/../../../../src/node/db/PadManager`);
const plugins = require(`${__dirname}/../../../../src/static/js/pluginfw/plugin_defs`);
const settings = require('ep_etherpad-lite/node/utils/Settings');
const padManager = require('ep_etherpad-lite/node/db/PadManager');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
const padText = fs.readFileSync('../tests/backend/specs/api/test.txt');
const etherpadDoc = fs.readFileSync('../tests/backend/specs/api/test.etherpad');
@ -122,7 +122,7 @@ describe(__filename, function () {
describe('Import/Export tests requiring AbiWord/LibreOffice', function () {
before(function () {
before(async function () {
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
this.skip();
@ -149,7 +149,8 @@ describe(__filename, function () {
await agent.post(`/p/${testPadId}/import`)
.attach('file', wordXDoc, {
filename: '/test.docx',
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
contentType:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
.expect(200)
.expect(/FrameCall\('undefined', 'ok'\);/);
@ -352,7 +353,7 @@ describe(__filename, function () {
}); // End of tests.
var endPoint = function (point, version) {
const endPoint = (point, version) => {
version = version || apiVersion;
return `/api/${version}/${point}?apikey=${apiKey}`;
};

View file

@ -1,27 +1,26 @@
const assert = require('assert');
os = require('os'),
fs = require('fs'),
path = require('path'),
TidyHtml = null,
Settings = null;
'use strict';
const npm = require('../../../../src/node_modules/npm/lib/npm.js');
const nodeify = require('../../../../src/node_modules/nodeify');
const assert = require('assert');
const os = require('os');
const fs = require('fs');
const path = require('path');
let TidyHtml;
let Settings;
const npm = require('ep_etherpad-lite/node_modules/npm/lib/npm.js');
const nodeify = require('ep_etherpad-lite/node_modules/nodeify');
describe(__filename, function () {
describe('tidyHtml', function () {
before(function (done) {
npm.load({}, (err) => {
assert.ok(!err);
TidyHtml = require('../../../../src/node/utils/TidyHtml');
Settings = require('../../../../src/node/utils/Settings');
TidyHtml = require('ep_etherpad-lite/node/utils/TidyHtml');
Settings = require('ep_etherpad-lite/node/utils/Settings');
return done();
});
});
function tidy(file, callback) {
return nodeify(TidyHtml.tidy(file), callback);
}
const tidy = (file, callback) => nodeify(TidyHtml.tidy(file), callback);
it('Tidies HTML', function (done) {
// If the user hasn't configured Tidy, we skip this tests as it's required for this test
@ -53,7 +52,7 @@ describe(__filename, function () {
'</html>',
].join('\n');
assert.notStrictEqual(cleanedHtml.indexOf(expectedHtml), -1);
return done();
done();
});
});
@ -65,7 +64,7 @@ describe(__filename, function () {
tidy('/some/none/existing/file.html', (err) => {
assert.ok(err);
return done();
done();
});
});
});