diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..61e9748e8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +52972ff01abab19a5861ac2364efc7471c336ae7 +d0530dbba74c87bbeb02433a490c49c28c1deb9d +8546f1fd2784ef579ef951c6d3ec4c20b1b156c5 +353fd24b7d01420d405377985dc5a43ea7b69498 \ No newline at end of file diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 000000000..7994bc363 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,2 @@ +[blame] + ignoreRevsFile = .git-blame-ignore-revs \ No newline at end of file diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 000000000..31354ec13 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..1df75316f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,8 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +# run prettier on staged files +npx lint-staged + +# typecheck +make lint \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..900872f5f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +*.min.js +*.min.css +build +src/intro.js +src/outro.js +test/support/jquery-1.5.2.js \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..dc2fb828f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} \ No newline at end of file diff --git a/Makefile b/Makefile index 38851776c..f178a9d94 100644 --- a/Makefile +++ b/Makefile @@ -102,7 +102,7 @@ BUILD_DIR_EXISTS = $(BUILD_DIR)/.exists--used_by_Makefile # -*- Build tasks -*- # -.PHONY: all basic dev js uglify css font clean +.PHONY: all basic dev js uglify css font clean setup-gitconfig prettify-all all: font css uglify basic: $(UGLY_BASIC_JS) $(BASIC_CSS) unminified_basic: $(BASIC_JS) $(BASIC_CSS) @@ -114,6 +114,14 @@ css: $(BUILD_CSS) font: $(FONT_TARGET) clean: rm -rf $(BUILD_DIR) +# This adds an entry to your local .git/config file that looks like this: +# [include] +# path = ../.gitconfig +# that tells git to include the additional configuration specified inside the .gitconfig file that's checked in here. +setup-gitconfig: + @git config --local include.path ../.gitconfig +prettify-all: + npx prettier --write '**/*.{ts,js,css,html}' $(BUILD_JS): $(INTRO) $(SOURCES_FULL) $(OUTRO) $(BUILD_DIR_EXISTS) cat $^ | ./script/escape-non-ascii | ./script/tsc-emit-only > $@ @@ -143,7 +151,7 @@ $(BASIC_CSS): $(CSS_SOURCES) $(NODE_MODULES_INSTALLED) $(BUILD_DIR_EXISTS) $(NODE_MODULES_INSTALLED): package.json test -e $(NODE_MODULES_INSTALLED) || rm -rf ./node_modules/ # robust against previous botched npm install - NODE_ENV=development npm install + NODE_ENV=development npm ci touch $(NODE_MODULES_INSTALLED) $(BUILD_DIR_EXISTS): @@ -169,7 +177,7 @@ test: dev $(BUILD_TEST) $(BASIC_JS) $(BASIC_CSS) @echo "** now open test/{unit,visual}.html in your browser to run the {unit,visual} tests. **" benchmark: dev $(BUILD_TEST) $(BASIC_JS) $(BASIC_CSS) @echo - @echo "** now open benchmark/select.html in your browser. **" + @echo "** now open benchmark/{render,select}.html in your browser. **" $(BUILD_TEST): $(INTRO) $(SOURCES_FULL) $(UNIT_TESTS) $(OUTRO) $(BUILD_DIR_EXISTS) cat $^ | ./script/tsc-emit-only > $@ diff --git a/benchmark/render.html b/benchmark/render.html new file mode 100644 index 000000000..5e313d8d5 --- /dev/null +++ b/benchmark/render.html @@ -0,0 +1,173 @@ + + + + + + + MathQuill Performance Test Page + + + + + + + + + + + +
+

MathQuill performance test page

+

+ This page randomly generates one or more latex expressions and times how + long it takes to mathquillify them. Query parameters: +

+ + + + +
+

All MQ render times:

+ +
+ + + + diff --git a/benchmark/select.html b/benchmark/select.html index e77904d66..0df40455f 100644 --- a/benchmark/select.html +++ b/benchmark/select.html @@ -1,77 +1,79 @@ - + + + - - + MathQuill Select benchmark -MathQuill Select benchmark + + + + +

Benchmark inserting and then selecting n characters

+ +
+ +
+ + + + + + + + +
ncharsrender (ms)select (ms)
+ + + - - - + document + .getElementById('run-button') + .addEventListener('click', function () { + runNext(); + }); + + diff --git a/package-lock.json b/package-lock.json index 6c41440cc..380dd8052 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,2137 @@ { "name": "mathquill", "version": "0.10.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "mathquill", + "version": "0.10.1", + "license": "MPL-2.0", + "devDependencies": { + "husky": "^7.0.4", + "less": ">=1.5.1 <3.0.0", + "lint-staged": "^12.3.1", + "mocha": ">=2.4.1", + "prettier": "^2.5.1", + "typescript": "^4.5.2", + "uglify-js": "2.x" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "optional": true, + "dependencies": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "node_modules/asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true, + "optional": true + }, + "node_modules/assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "node_modules/aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true, + "optional": true + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "optional": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "optional": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "boom": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dashdash/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "dependencies": { + "jsbn": "~0.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true, + "optional": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "optional": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/getpass/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "deprecated": "this library is no longer supported", + "dev": true, + "optional": true, + "dependencies": { + "ajv": "^4.9.1", + "har-schema": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + }, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", + "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "optional": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "optional": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/jsprim/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/less": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", + "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", + "dev": true, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=0.12" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.2.11", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "2.81.0", + "source-map": "^0.5.3" + } + }, + "node_modules/lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lint-staged": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.1.tgz", + "integrity": "sha512-Ocht/eT+4/siWOZDJpNUKcKX2UeWW/pDbohJ4gRsrafAjBi79JK8kiNVk2ciIVNKdw0Q4ABptl2nr6uQAlRImw==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.16", + "commander": "^8.3.0", + "debug": "^4.3.3", + "execa": "^5.1.1", + "lilconfig": "2.0.4", + "listr2": "^4.0.1", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.0", + "string-argv": "^0.3.1", + "supports-color": "^9.2.1", + "yaml": "^1.10.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/lint-staged/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/lint-staged/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/lint-staged/node_modules/supports-color": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", + "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.1.tgz", + "integrity": "sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.2", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "optional": true, + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz", + "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==", + "dev": true, + "dependencies": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true, + "optional": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true, + "optional": true + }, + "node_modules/qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "optional": true, + "dependencies": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.2.tgz", + "integrity": "sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true, + "optional": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "optional": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "dashdash": "^1.12.0", + "getpass": "^0.1.1" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + }, + "optionalDependencies": { + "bcrypt-pbkdf": "^1.0.0", + "ecc-jsbn": "~0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "node_modules/sshpk/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.0.tgz", + "integrity": "sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true, + "optional": true + }, + "node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "optional": true, + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "node_modules/uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + }, "dependencies": { + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", @@ -26,6 +2154,27 @@ "repeat-string": "^1.5.2" } }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "dev": true + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -47,6 +2196,12 @@ "dev": true, "optional": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -104,6 +2259,15 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", @@ -133,6 +2297,31 @@ "lazy-cache": "^1.0.3" } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -151,6 +2340,27 @@ "dev": true, "optional": true }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -180,6 +2390,17 @@ "dev": true, "optional": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", @@ -237,6 +2458,12 @@ "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -247,6 +2474,12 @@ "jsbn": "~0.1.0" } }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -263,6 +2496,23 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -277,6 +2527,15 @@ "dev": true, "optional": true }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -302,6 +2561,12 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -410,6 +2675,18 @@ "sshpk": "^1.7.0" } }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "husky": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", + "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "dev": true + }, "image-size": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", @@ -417,6 +2694,12 @@ "dev": true, "optional": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -439,6 +2722,24 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -446,6 +2747,12 @@ "dev": true, "optional": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -544,12 +2851,253 @@ "source-map": "^0.5.3" } }, + "lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true + }, + "lint-staged": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.1.tgz", + "integrity": "sha512-Ocht/eT+4/siWOZDJpNUKcKX2UeWW/pDbohJ4gRsrafAjBi79JK8kiNVk2ciIVNKdw0Q4ABptl2nr6uQAlRImw==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.16", + "commander": "^8.3.0", + "debug": "^4.3.3", + "execa": "^5.1.1", + "lilconfig": "2.0.4", + "listr2": "^4.0.1", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.0", + "string-argv": "^0.3.1", + "supports-color": "^9.2.1", + "yaml": "^1.10.2" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", + "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", + "dev": true + } + } + }, + "listr2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.1.tgz", + "integrity": "sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.2", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -574,6 +3122,12 @@ "mime-db": "~1.33.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -622,6 +3176,21 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -629,6 +3198,12 @@ "dev": true, "optional": true }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -638,12 +3213,36 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", @@ -651,6 +3250,18 @@ "dev": true, "optional": true }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -719,6 +3330,22 @@ "uuid": "^3.0.0" } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -728,6 +3355,15 @@ "align-text": "^0.1.1" } }, + "rxjs": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.2.tgz", + "integrity": "sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -735,6 +3371,37 @@ "dev": true, "optional": true }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + } + }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -777,6 +3444,23 @@ } } }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, + "string-width": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.0.tgz", + "integrity": "sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -784,6 +3468,21 @@ "dev": true, "optional": true }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", @@ -793,6 +3492,21 @@ "has-flag": "^2.0.0" } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", @@ -803,6 +3517,12 @@ "punycode": "^1.4.1" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -820,6 +3540,12 @@ "dev": true, "optional": true }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, "typescript": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", @@ -872,6 +3598,15 @@ } } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -884,12 +3619,78 @@ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", "dev": true }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", diff --git a/package.json b/package.json index 62ef80687..54dafa5f4 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,18 @@ "quickstart.html" ], "devDependencies": { + "husky": "^7.0.4", "less": ">=1.5.1 <3.0.0", + "lint-staged": "^12.3.1", "mocha": ">=2.4.1", + "prettier": "^2.5.1", "typescript": "^4.5.2", "uglify-js": "2.x" + }, + "lint-staged": { + "*.{ts,js,css,html}": "prettier --write" + }, + "scripts": { + "prepare": "husky install" } } diff --git a/quickstart.html b/quickstart.html index 0bb332769..3eb37bbb8 100644 --- a/quickstart.html +++ b/quickstart.html @@ -2,14 +2,20 @@ MathQuill Quickstart - + -

Static math span: x = \frac{ -b \pm \sqrt{b^2-4ac} }{ 2a } +

+ Static math span: + x = \frac{ -b \pm \sqrt{b^2-4ac} }{ 2a } +

Editable math field: x^2

LaTeX of what you typed: x^2

-

MathQuill’s Getting - Started Guide

+

+ MathQuill’s Getting Started Guide +

@@ -27,13 +33,13 @@ // you may pass in an options object: var mathField = MQ.MathField(mathFieldSpan, { spaceBehavesLikeTab: true, // an example config option, for more see: - // http://docs.mathquill.com/en/latest/Config/ + // http://docs.mathquill.com/en/latest/Config/ handlers: { - edit: function() { + edit: function () { // retrieve, in LaTeX format, the math that was typed: latexSpan.textContent = mathField.latex(); - } - } + }, + }, }); diff --git a/script/screenshots.js b/script/screenshots.js index 547669b5f..ac34f37c0 100644 --- a/script/screenshots.js +++ b/script/screenshots.js @@ -20,185 +20,238 @@ var accessKey = process.env.SAUCE_ACCESS_KEY; var build_name = process.env.MQ_CI_BUILD_NAME; var baseDir = process.env.CIRCLE_ARTIFACTS; if (!baseDir) { - console.error('No $CIRCLE_ARTIFACTS found, for testing do something like `CIRCLE_ARTIFACTS=/tmp script/screenshots.js`'); + console.error( + 'No $CIRCLE_ARTIFACTS found, for testing do something like `CIRCLE_ARTIFACTS=/tmp script/screenshots.js`' + ); process.exit(1); } -fs.mkdirSync(baseDir+'/imgs'); -fs.mkdirSync(baseDir+'/imgs/pieces'); -fs.mkdirSync(baseDir+'/browser_logs'); +fs.mkdirSync(baseDir + '/imgs'); +fs.mkdirSync(baseDir + '/imgs/pieces'); +fs.mkdirSync(baseDir + '/browser_logs'); var browsers = [ { config: { browserName: 'Internet Explorer', - platform: 'Windows XP' + platform: 'Windows XP', }, - pinned: true // assume pinned to IE 8 + pinned: true, // assume pinned to IE 8 }, { config: { browserName: 'Internet Explorer', - platform: 'Windows 7' + platform: 'Windows 7', }, - pinned: true // assume pinned to IE 11 + pinned: true, // assume pinned to IE 11 }, { config: { browserName: 'MicrosoftEdge', - platform: 'Windows 10' - } + platform: 'Windows 10', + }, }, { config: { browserName: 'Firefox', - platform: 'OS X 10.11' - } + platform: 'OS X 10.11', + }, }, { config: { browserName: 'Safari', - platform: 'OS X 10.11' - } + platform: 'OS X 10.11', + }, }, { config: { browserName: 'Chrome', - platform: 'OS X 10.11' - } + platform: 'OS X 10.11', + }, }, { config: { browserName: 'Firefox', - platform: 'Linux' - } - } + platform: 'Linux', + }, + }, ]; - -browsers.forEach(function(browser) { +browsers.forEach(function (browser) { browser.config.build = build_name; - browser.config.name = 'Visual tests, ' + browser.config.browserName + ' on ' + browser.config.platform; - browser.config.customData = {build_url: process.env.CIRCLE_BUILD_URL}; - var browserDriver = wd.promiseChainRemote('ondemand.saucelabs.com', 80, username, accessKey); - return browserDriver.init(browser.config) - .then(function(args) { - var cfg = browser.config, capabilities = args[1]; - var version = capabilities.version || capabilities.browserVersion; - var sessionName = [cfg.browserName, version, cfg.platform].join(' '); - if (capabilities.platformVersion) sessionName += ' ' + capabilities.platformVersion; - console.log(sessionName, 'init', args); + browser.config.name = + 'Visual tests, ' + + browser.config.browserName + + ' on ' + + browser.config.platform; + browser.config.customData = { build_url: process.env.CIRCLE_BUILD_URL }; + var browserDriver = wd.promiseChainRemote( + 'ondemand.saucelabs.com', + 80, + username, + accessKey + ); + return browserDriver + .init(browser.config) + .then(function (args) { + var cfg = browser.config, + capabilities = args[1]; + var version = capabilities.version || capabilities.browserVersion; + var sessionName = [cfg.browserName, version, cfg.platform].join(' '); + if (capabilities.platformVersion) + sessionName += ' ' + capabilities.platformVersion; + console.log(sessionName, 'init', args); - var evergreen = browser.pinned ? '' : '_(evergreen)'; - var fileName = [cfg.browserName, version + evergreen, cfg.platform].join('_'); - if (capabilities.platformVersion) fileName += ' ' + capabilities.platformVersion; - fileName = fileName.replace(/ /g, '_'); + var evergreen = browser.pinned ? '' : '_(evergreen)'; + var fileName = [cfg.browserName, version + evergreen, cfg.platform].join( + '_' + ); + if (capabilities.platformVersion) + fileName += ' ' + capabilities.platformVersion; + fileName = fileName.replace(/ /g, '_'); - return browserDriver.get(url) - .then(willLog(sessionName, 'get')) - .safeExecute('document.body.focus()') // blur anything that's auto-focused - .then(willLog(sessionName, 'document.body.focus()')) - .safeExecute('document.documentElement.style.overflow = "hidden"') // hide scrollbars - .then(willLog(sessionName, 'hide scrollbars')) - .then(function() { - // Microsoft Edge starts out with illegally big window: https://git.io/vD63O - if (cfg.browserName === 'MicrosoftEdge') { - return browserDriver.getWindowSize() - .then(function(size) { - return browserDriver.setWindowSize(size.width, size.height) + return browserDriver + .get(url) + .then(willLog(sessionName, 'get')) + .safeExecute('document.body.focus()') // blur anything that's auto-focused + .then(willLog(sessionName, 'document.body.focus()')) + .safeExecute('document.documentElement.style.overflow = "hidden"') // hide scrollbars + .then(willLog(sessionName, 'hide scrollbars')) + .then(function () { + // Microsoft Edge starts out with illegally big window: https://git.io/vD63O + if (cfg.browserName === 'MicrosoftEdge') { + return browserDriver + .getWindowSize() + .then(function (size) { + return browserDriver.setWindowSize(size.width, size.height); + }) + .then( + willLog(sessionName, 'reset window size (Edge-only workaround)') + ); + } }) - .then(willLog(sessionName, 'reset window size (Edge-only workaround)')) - } - }) - .then(function() { - return [browserDriver.safeExecute('document.documentElement.scrollHeight'), - browserDriver.safeExecute('document.documentElement.clientHeight')]; - }) - .spread(function(scrollHeight, viewportHeight) { - console.log(sessionName, 'get scrollHeight, clientHeight', scrollHeight, viewportHeight); + .then(function () { + return [ + browserDriver.safeExecute('document.documentElement.scrollHeight'), + browserDriver.safeExecute('document.documentElement.clientHeight'), + ]; + }) + .spread(function (scrollHeight, viewportHeight) { + console.log( + sessionName, + 'get scrollHeight, clientHeight', + scrollHeight, + viewportHeight + ); - // the easy case: IE and Firefox on Linux return a screenshot of the entire webpage - if (cfg.browserName === 'Internet Explorer'|| (cfg.browserName === 'Firefox' && cfg.platform === 'Linux')) { - return browserDriver.saveScreenshot(baseDir + '/imgs/' + fileName + '.png') - .then(willLog(sessionName, 'saveScreenshot')) - // the hard case: for Chrome, Safari, and Edge, scroll through the page and - // take screenshots of each piece; circle.yml will stitch them together - } else { - var piecesDir = baseDir + '/imgs/pieces/' + fileName + '/'; - fs.mkdirSync(piecesDir); + // the easy case: IE and Firefox on Linux return a screenshot of the entire webpage + if ( + cfg.browserName === 'Internet Explorer' || + (cfg.browserName === 'Firefox' && cfg.platform === 'Linux') + ) { + return browserDriver + .saveScreenshot(baseDir + '/imgs/' + fileName + '.png') + .then(willLog(sessionName, 'saveScreenshot')); + // the hard case: for Chrome, Safari, and Edge, scroll through the page and + // take screenshots of each piece; circle.yml will stitch them together + } else { + var piecesDir = baseDir + '/imgs/pieces/' + fileName + '/'; + fs.mkdirSync(piecesDir); - var scrollTop = 0; - var index = 1; + var scrollTop = 0; + var index = 1; - return (function loop() { - return browserDriver.safeEval('window.scrollTo(0,'+scrollTop+');') - .then(willLog(sessionName, 'scrollTo()')) - .saveScreenshot(piecesDir + index + '.png') - .then(function() { - console.log(sessionName, 'saveScreenshot'); + return (function loop() { + return browserDriver + .safeEval('window.scrollTo(0,' + scrollTop + ');') + .then(willLog(sessionName, 'scrollTo()')) + .saveScreenshot(piecesDir + index + '.png') + .then(function () { + console.log(sessionName, 'saveScreenshot'); - scrollTop += viewportHeight; - index += 1; + scrollTop += viewportHeight; + index += 1; - // if the viewport hasn't passed the bottom edge of the page yet, - // scroll down and take another screenshot - if (scrollTop + viewportHeight <= scrollHeight) { - // Use `window.scrollTo` because thats what jQuery does: - // https://github.com/jquery/jquery/blob/1.12.3/src/offset.js#L186 - // Use `window.scrollTo` instead of jQuery because jQuery was - // causing a stackoverflow in Safari. - return loop(); - } else { // we are past the bottom edge of the page, reduce window size to - // fit only the part of the page that hasn't been screenshotted. + // if the viewport hasn't passed the bottom edge of the page yet, + // scroll down and take another screenshot + if (scrollTop + viewportHeight <= scrollHeight) { + // Use `window.scrollTo` because thats what jQuery does: + // https://github.com/jquery/jquery/blob/1.12.3/src/offset.js#L186 + // Use `window.scrollTo` instead of jQuery because jQuery was + // causing a stackoverflow in Safari. + return loop(); + } else { + // we are past the bottom edge of the page, reduce window size to + // fit only the part of the page that hasn't been screenshotted. - // If there is no remaining part of the page, we're done, short-circuit - if (scrollTop === scrollHeight) return browserDriver; + // If there is no remaining part of the page, we're done, short-circuit + if (scrollTop === scrollHeight) return browserDriver; - return browserDriver.getWindowSize() - .then(function(windowSize) { - console.log(sessionName, 'getWindowSize'); - // window size is a little bigger than the viewport because of address - // bar and scrollbars and stuff - var windowPadding = windowSize.height - viewportHeight; - var newWindowHeight = scrollHeight - scrollTop + windowPadding; - return browserDriver.setWindowSize(windowSize.width, newWindowHeight) - .then(willLog(sessionName, 'setWindowSize')) - .safeEval('window.scrollTo(0,'+scrollHeight+');') - .then(willLog(sessionName, 'scrollTo() Final')) - .saveScreenshot(piecesDir + index + '.png') - .then(willLog(sessionName, 'saveScreenshot Final')); - }); + return browserDriver + .getWindowSize() + .then(function (windowSize) { + console.log(sessionName, 'getWindowSize'); + // window size is a little bigger than the viewport because of address + // bar and scrollbars and stuff + var windowPadding = windowSize.height - viewportHeight; + var newWindowHeight = + scrollHeight - scrollTop + windowPadding; + return browserDriver + .setWindowSize(windowSize.width, newWindowHeight) + .then(willLog(sessionName, 'setWindowSize')) + .safeEval('window.scrollTo(0,' + scrollHeight + ');') + .then(willLog(sessionName, 'scrollTo() Final')) + .saveScreenshot(piecesDir + index + '.png') + .then(willLog(sessionName, 'saveScreenshot Final')); + }); + } + }); + })(); + } + }) + .then(function () { + return browserDriver.log('browser').then( + function (logs) { + var logfile = + baseDir + + '/browser_logs/' + + sessionName.replace(/ /g, '_') + + '.log'; + return new Promise(function (resolve, reject) { + fs.writeFile( + logfile, + JSON.stringify(logs, null, 2), + function (err) { + err ? reject(err) : resolve(); + } + ); + }).then(willLog(sessionName, 'writeFile')); + }, + function (err) { + // the Edge, IE, and Firefox-on-macOS drivers don't support logs, but the others do + console.log( + sessionName, + 'Error fetching logs:', + JSON.stringify(err, null, 2) + ); } - }); - }()); - } + ); + }); }) - .then(function() { - return browserDriver.log('browser') - .then(function(logs) { - var logfile = baseDir + '/browser_logs/' + sessionName.replace(/ /g, '_') + '.log'; - return new Promise(function(resolve, reject) { - fs.writeFile(logfile, JSON.stringify(logs, null, 2), function(err) { - err ? reject(err) : resolve(); - }); - }) - .then(willLog(sessionName, 'writeFile')); - }, function(err) { - // the Edge, IE, and Firefox-on-macOS drivers don't support logs, but the others do - console.log(sessionName, 'Error fetching logs:', JSON.stringify(err, null, 2)); - }); - }); - }) - .sauceJobStatus(true) - .fail(function(err) { - console.log('ERROR:', browser.config.browserName, browser.config.platform); - console.log(JSON.stringify(err, null, 2)); - return browserDriver.sauceJobStatus(false); - }) - .quit(); + .sauceJobStatus(true) + .fail(function (err) { + console.log( + 'ERROR:', + browser.config.browserName, + browser.config.platform + ); + console.log(JSON.stringify(err, null, 2)); + return browserDriver.sauceJobStatus(false); + }) + .quit(); function willLog() { var msg = [].join.call(arguments, ' '); - return function(value) { + return function (value) { console.log(msg); return value; }; diff --git a/script/test_server.js b/script/test_server.js index b653eb424..c734a1d5e 100644 --- a/script/test_server.js +++ b/script/test_server.js @@ -11,80 +11,91 @@ var HOST = process.env.HOST || '0.0.0.0'; // main http.createServer(serveRequest).listen(PORT, HOST); -console.log('listening on '+HOST+':'+PORT); +console.log('listening on ' + HOST + ':' + PORT); run_make_test(); -'src test Makefile package.json'.split(' ').forEach(function(filename) { +'src test Makefile package.json'.split(' ').forEach(function (filename) { recursivelyWatch(filename, run_make_test); }); // functions function serveRequest(req, res) { - var reqTime = new Date; - enqueueOrDo(function() { + var reqTime = new Date(); + enqueueOrDo(function () { var filepath = path.normalize(url.parse(req.url).pathname).slice(1); - fs.readFile(filepath, function(err, data) { + fs.readFile(filepath, function (err, data) { if (err) { if (err.code === 'ENOENT' || err.code === 'EISDIR') { res.statusCode = 404; res.end('404 Not Found: /' + filepath + '\n'); - } - else { + } else { console.log(err); res.statusCode = 500; res.end('500 Internal Server Error: ' + err.code + '\n'); } - } - else { + } else { var ext = filepath.match(/\.[^.]+$/); if (ext) res.setHeader('Content-Type', 'text/' + ext[0].slice(1)); res.end(data); } - console.log('[%s] %s %s /%s - %s%sms', - reqTime.toISOString(), res.statusCode, req.method, filepath, - (data ? (data.length >> 10) + 'kb, ' : ''), Date.now() - reqTime); + console.log( + '[%s] %s %s /%s - %s%sms', + reqTime.toISOString(), + res.statusCode, + req.method, + filepath, + data ? (data.length >> 10) + 'kb, ' : '', + Date.now() - reqTime + ); }); }); } - function recursivelyWatch(watchee, cb) { - fs.readdir(watchee, function(err, files) { - if (err) { // not a directory, just watch it + fs.readdir(watchee, function (err, files) { + if (err) { + // not a directory, just watch it fs.watch(watchee, cb); - } - else { // a directory, recurse, also watch for files being added or deleted + } else { + // a directory, recurse, also watch for files being added or deleted files.forEach(recurse); - fs.watch(watchee, function() { - fs.readdir(watchee, function(err, filesNew) { + fs.watch(watchee, function () { + fs.readdir(watchee, function (err, filesNew) { if (err) return; // watchee may have been deleted // filesNew - files = new files or dirs to watch - filesNew.filter(function(file) { return files.indexOf(file) < 0; }) - .forEach(recurse); + filesNew + .filter(function (file) { + return files.indexOf(file) < 0; + }) + .forEach(recurse); files = filesNew; }); cb(); }); } - function recurse(file) { recursivelyWatch(path.join(watchee, file), cb); } + function recurse(file) { + recursivelyWatch(path.join(watchee, file), cb); + } }); } - var q; -function enqueueOrDo(cb) { q ? q.push(cb) : cb(); } +function enqueueOrDo(cb) { + q ? q.push(cb) : cb(); +} function run_make_test() { if (q) return; q = []; - console.log('[%s]\nmake test', (new Date).toISOString()); + console.log('[%s]\nmake test', new Date().toISOString()); var make_test = child_process.exec('make test', { env: process.env }); make_test.stdout.pipe(process.stdout, { end: false }); make_test.stderr.pipe(process.stderr, { end: false }); - make_test.on('exit', function(code) { + make_test.on('exit', function (code) { if (code) { console.error('Exit Code ' + code); } else { - var serverAddress = HOST === '0.0.0.0' ? 'localhost:' + PORT : HOST + ':' + PORT; + var serverAddress = + HOST === '0.0.0.0' ? 'localhost:' + PORT : HOST + ':' + PORT; console.log('\nMathQuill is now running on ' + serverAddress); console.log('Open http://' + serverAddress + '/test/demo.html\n'); } diff --git a/src/commands/math.ts b/src/commands/math.ts index 24dea4b82..abd243582 100644 --- a/src/commands/math.ts +++ b/src/commands/math.ts @@ -8,18 +8,26 @@ * Both MathBlock's and MathCommand's descend from it. */ class MathElement extends MQNode { - finalizeInsert (options:CursorOptions, cursor:Cursor) { + finalizeInsert(options: CursorOptions, cursor: Cursor) { var self = this; - self.postOrder(function (node) { node.finalizeTree(options) }); - self.postOrder(function (node) { node.contactWeld(cursor) }); + self.postOrder(function (node) { + node.finalizeTree(options); + }); + self.postOrder(function (node) { + node.contactWeld(cursor); + }); // note: this order is important. // empty elements need the empty box provided by blur to // be present in order for their dimensions to be measured // correctly by 'reflow' handlers. - self.postOrder(function (node) { node.blur(cursor); }); + self.postOrder(function (node) { + node.blur(cursor); + }); - self.postOrder(function (node) { node.reflow(); }); + self.postOrder(function (node) { + node.reflow(); + }); var selfR = self[R]; var selfL = self[L]; if (selfR) selfR.siblingCreated(options, L); @@ -28,35 +36,35 @@ class MathElement extends MQNode { node.reflow(); return undefined; }); - }; + } // If the maxDepth option is set, make sure // deeply nested content is truncated. Just return // false if the cursor is already too deep. - prepareInsertionAt (cursor:Cursor) { + prepareInsertionAt(cursor: Cursor) { var maxDepth = cursor.options.maxDepth; if (maxDepth !== undefined) { var cursorDepth = cursor.depth(); if (cursorDepth > maxDepth) { return false; } - this.removeNodesDeeperThan(maxDepth-cursorDepth); + this.removeNodesDeeperThan(maxDepth - cursorDepth); } return true; - }; + } // Remove nodes that are more than `cutoff` // blocks deep from this node. - removeNodesDeeperThan (cutoff:number) { + removeNodesDeeperThan(cutoff: number) { var depth = 0; - var queue:[[MQNode, number]] = [[this, depth]]; - var current:[MQNode, number] | undefined; + var queue: [[MQNode, number]] = [[this, depth]]; + var current: [MQNode, number] | undefined; // Do a breadth-first search of this node's descendants // down to cutoff, removing anything deeper. - while (current = queue.shift()) { + while ((current = queue.shift())) { var c = current; c[0].children().each(function (child) { - var i = (child instanceof MathBlock) ? 1 : 0; - depth = c[1]+i; + var i = child instanceof MathBlock ? 1 : 0; + depth = c[1] + i; if (depth <= cutoff) { queue.push([child, depth]); @@ -66,7 +74,7 @@ class MathElement extends MQNode { return undefined; }); } - }; + } } /** @@ -74,31 +82,39 @@ class MathElement extends MQNode { * Descendant commands are organized into blocks. */ class MathCommand extends MathElement { - replacedFragment:Fragment | undefined; + replacedFragment: Fragment | undefined; - constructor (ctrlSeq?:string, htmlTemplate?:string, textTemplate?:string[]) { + constructor( + ctrlSeq?: string, + htmlTemplate?: string, + textTemplate?: string[] + ) { super(); this.setCtrlSeqHtmlAndText(ctrlSeq, htmlTemplate, textTemplate); } - setCtrlSeqHtmlAndText (ctrlSeq?:string, htmlTemplate?:string, textTemplate?:string[]) { + setCtrlSeqHtmlAndText( + ctrlSeq?: string, + htmlTemplate?: string, + textTemplate?: string[] + ) { if (!this.ctrlSeq) this.ctrlSeq = ctrlSeq; if (htmlTemplate) this.htmlTemplate = htmlTemplate; if (textTemplate) this.textTemplate = textTemplate; } // obvious methods - replaces (replacedFragment:Fragment) { + replaces(replacedFragment: Fragment) { replacedFragment.disown(); this.replacedFragment = replacedFragment; - }; - isEmpty () { - return this.foldChildren(true, function(isEmpty, child) { + } + isEmpty() { + return this.foldChildren(true, function (isEmpty, child) { return isEmpty && child.isEmpty(); }); - }; + } - parser ():Parser { + parser(): Parser { var block = latexMathParser.block; return block.times(this.numBlocks()).map((blocks) => { @@ -110,10 +126,10 @@ class MathCommand extends MathElement { return this; }); - }; + } // createLeftOf(cursor) and the methods it calls - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { var cmd = this; var replacedFragment = cmd.replacedFragment; @@ -128,30 +144,32 @@ class MathCommand extends MathElement { } cmd.finalizeInsert(cursor.options, cursor); cmd.placeCursor(cursor); - }; - createBlocks () { + } + createBlocks() { var cmd = this, numBlocks = cmd.numBlocks(), - blocks = cmd.blocks = Array(numBlocks); + blocks = (cmd.blocks = Array(numBlocks)); for (var i = 0; i < numBlocks; i += 1) { - var newBlock = blocks[i] = new MathBlock(); + var newBlock = (blocks[i] = new MathBlock()); newBlock.adopt(cmd, cmd.ends[R], 0); } - }; - placeCursor (cursor:Cursor) { + } + placeCursor(cursor: Cursor) { //insert the cursor at the right end of the first empty child, searching //left-to-right, or if none empty, the right end child - cursor.insAtRightEnd(this.foldChildren(this.ends[L] as MQNode, function(leftward, child) { - return leftward.isEmpty() ? leftward : child; - })); - }; + cursor.insAtRightEnd( + this.foldChildren(this.ends[L] as MQNode, function (leftward, child) { + return leftward.isEmpty() ? leftward : child; + }) + ); + } // editability methods: called by the cursor for editing, cursor movements, // and selection of the MathQuill tree, these all take in a direction and // the cursor - moveTowards (dir:Direction, cursor:Cursor, updown?:'up'|'down') { - var updownInto:NodeRef | undefined; + moveTowards(dir: Direction, cursor: Cursor, updown?: 'up' | 'down') { + var updownInto: NodeRef | undefined; if (updown === 'up') { updownInto = this.upInto; } else if (updown === 'down') { @@ -160,32 +178,34 @@ class MathCommand extends MathElement { const el = (updownInto || this.ends[-dir as Direction]) as MQNode; cursor.insAtDirEnd(-dir as Direction, el); - cursor.controller.aria.queueDirEndOf(-dir as Direction).queue(cursor.parent, true); - }; - deleteTowards (dir:Direction, cursor:Cursor) { + cursor.controller.aria + .queueDirEndOf(-dir as Direction) + .queue(cursor.parent, true); + } + deleteTowards(dir: Direction, cursor: Cursor) { if (this.isEmpty()) cursor[dir] = this.remove()[dir]; else this.moveTowards(dir, cursor); - }; - selectTowards (dir:Direction, cursor:Cursor) { + } + selectTowards(dir: Direction, cursor: Cursor) { cursor[-dir as Direction] = this; cursor[dir] = this[dir]; - }; - selectChildren ():MQSelection { + } + selectChildren(): MQSelection { return new MQSelection(this, this); - }; - unselectInto (dir:Direction, cursor:Cursor) { + } + unselectInto(dir: Direction, cursor: Cursor) { const antiCursor = cursor.anticursor as Anticursor; const ancestor = antiCursor.ancestors[this.id] as MQNode; cursor.insAtDirEnd(-dir as Direction, ancestor); - }; - seek (pageX:number, cursor:Cursor) { - function getBounds(node:MQNode) { - var l:number = node.jQ.offset().left; - var r:number = l + node.jQ.outerWidth(); + } + seek(pageX: number, cursor: Cursor) { + function getBounds(node: MQNode) { + var l: number = node.jQ.offset().left; + var r: number = l + node.jQ.outerWidth(); return { [L]: l, - [R]: r - } + [R]: r, + }; } var cmd = this; @@ -195,29 +215,27 @@ class MathCommand extends MathElement { if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd); var leftLeftBound = cmdBounds[L]; - cmd.eachChild(function(block) { + cmd.eachChild(function (block) { var blockBounds = getBounds(block); if (pageX < blockBounds[L]) { // closer to this block's left bound, or the bound left of that? if (pageX - leftLeftBound < blockBounds[L] - pageX) { if (block[L]) cursor.insAtRightEnd(block[L] as MQNode); else cursor.insLeftOf(cmd); - } - else cursor.insAtLeftEnd(block); + } else cursor.insAtLeftEnd(block); return false; - } - else if (pageX > blockBounds[R]) { - if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block - else { // last (rightmost) block + } else if (pageX > blockBounds[R]) { + if (block[R]) leftLeftBound = blockBounds[R]; + // continue to next block + else { + // last (rightmost) block // closer to this block's right bound, or the cmd's right bound? if (cmdBounds[R] - pageX < pageX - blockBounds[R]) { cursor.insRightOf(cmd); - } - else cursor.insAtRightEnd(block); + } else cursor.insAtRightEnd(block); } return undefined; - } - else { + } else { block.seek(pageX, cursor); return false; } @@ -252,11 +270,11 @@ class MathCommand extends MathElement { Note that & isn't well-formed HTML; if you wanted a literal '&123', your HTML template would have to have '&123'. */ - numBlocks () { + numBlocks() { var matches = (this.htmlTemplate as string).match(/&\d+/g); return matches ? matches.length : 0; - }; - html () { + } + html() { // Render the entire math subtree rooted at this command, as HTML. // Expects .createBlocks() to have been called already, since it uses the // .blocks array of child blocks. @@ -295,7 +313,9 @@ class MathCommand extends MathElement { var cmd = this; var blocks = cmd.blocks as MathBlock[]; var cmdId = ' mathquill-command-id=' + cmd.id; - var tokens = (cmd.htmlTemplate as string).match(/<[^<>]+>|[^<>]+/g) as string[]; + var tokens = (cmd.htmlTemplate as string).match( + /<[^<>]+>|[^<>]+/g + ) as string[]; pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate); @@ -305,21 +325,21 @@ class MathCommand extends MathElement { for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) { // top-level self-closing tags if (token.slice(-2) === '/>') { - tokens[i] = token.slice(0,-2) + cmdId + ' aria-hidden="true"/>'; + tokens[i] = token.slice(0, -2) + cmdId + ' aria-hidden="true"/>'; } // top-level open tags else if (token.charAt(0) === '<') { pray('not an unmatched top-level close tag', token.charAt(1) !== '/'); - tokens[i] = token.slice(0,-1) + cmdId + ' aria-hidden="true">'; + tokens[i] = token.slice(0, -1) + cmdId + ' aria-hidden="true">'; // skip matching top-level close tag and all tag pairs in between var nesting = 1; do { - i += 1, token = tokens[i]; + (i += 1), (token = tokens[i]); pray('no missing close tags', token); // close tags - if (token.slice(0,2) === ' 0); } } - return tokens.join('').replace(/>&(\d+)/g, function(_$0:string, $1:string) { - var num1 = parseInt($1, 10); - return ' mathquill-block-id=' + blocks[num1].id + ' aria-hidden="true">' + blocks[num1].join('html'); - }); - }; + return tokens + .join('') + .replace(/>&(\d+)/g, function (_$0: string, $1: string) { + var num1 = parseInt($1, 10); + return ( + ' mathquill-block-id=' + + blocks[num1].id + + ' aria-hidden="true">' + + blocks[num1].join('html') + ); + }); + } // methods to export a string representation of the math tree - latex () { - return this.foldChildren(this.ctrlSeq || '', function(latex, child) { + latex() { + return this.foldChildren(this.ctrlSeq || '', function (latex, child) { return latex + '{' + (child.latex() || ' ') + '}'; }); - }; + } textTemplate = ['']; - text () { - var cmd = this, i = 0; - return cmd.foldChildren(cmd.textTemplate[i], function(text, child) { + text() { + var cmd = this, + i = 0; + return cmd.foldChildren(cmd.textTemplate[i], function (text, child) { i += 1; var child_text = child.text(); - if (text && cmd.textTemplate[i] === '(' - && child_text[0] === '(' && child_text.slice(-1) === ')') + if ( + text && + cmd.textTemplate[i] === '(' && + child_text[0] === '(' && + child_text.slice(-1) === ')' + ) return text + child_text.slice(1, -1) + cmd.textTemplate[i]; return text + child_text + (cmd.textTemplate[i] || ''); }); - }; + } mathspeakTemplate = ['']; - mathspeak () { - var cmd = this, i = 0; - return cmd.foldChildren(cmd.mathspeakTemplate[i] || 'Start'+cmd.ctrlSeq+' ', function(speech, block) { - i += 1; - return speech + ' ' + block.mathspeak() + ' ' + (cmd.mathspeakTemplate[i]+' ' || 'End'+cmd.ctrlSeq+' '); - }); - }; -}; + mathspeak() { + var cmd = this, + i = 0; + return cmd.foldChildren( + cmd.mathspeakTemplate[i] || 'Start' + cmd.ctrlSeq + ' ', + function (speech, block) { + i += 1; + return ( + speech + + ' ' + + block.mathspeak() + + ' ' + + (cmd.mathspeakTemplate[i] + ' ' || 'End' + cmd.ctrlSeq + ' ') + ); + } + ); + } +} /** * Lightweight command without blocks or children. */ class MQSymbol extends MathCommand { - constructor (ctrlSeq?:string, html?:string, text?:string, mathspeak?:string) { + constructor( + ctrlSeq?: string, + html?: string, + text?: string, + mathspeak?: string + ) { super(); this.setCtrlSeqHtmlTextAndMathspeak(ctrlSeq, html, text, mathspeak); - }; + } - setCtrlSeqHtmlTextAndMathspeak (ctrlSeq?:string, html?:string, text?:string, mathspeak?:string) { + setCtrlSeqHtmlTextAndMathspeak( + ctrlSeq?: string, + html?: string, + text?: string, + mathspeak?: string + ) { if (!text && !!ctrlSeq) { text = ctrlSeq.replace(/^\\/, ''); } @@ -381,58 +433,90 @@ class MQSymbol extends MathCommand { super.setCtrlSeqHtmlAndText(ctrlSeq, html, [text || '']); } - parser () { return Parser.succeed(this); }; - numBlocks () { return 0; }; + parser() { + return Parser.succeed(this); + } + numBlocks() { + return 0; + } - replaces (replacedFragment:Fragment) { + replaces(replacedFragment: Fragment) { replacedFragment.remove(); - }; - createBlocks () {}; + } + createBlocks() {} - moveTowards (dir:Direction, cursor:Cursor) { + moveTowards(dir: Direction, cursor: Cursor) { cursor.jQ.insDirOf(dir, this.jQ); cursor[-dir as Direction] = this; cursor[dir] = this[dir]; cursor.controller.aria.queue(this); - }; - deleteTowards (dir:Direction, cursor:Cursor) { + } + deleteTowards(dir: Direction, cursor: Cursor) { cursor[dir] = this.remove()[dir]; - }; - seek (pageX:number, cursor:Cursor) { + } + seek(pageX: number, cursor: Cursor) { // insert at whichever side the click was closer to - if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2) + if (pageX - this.jQ.offset().left < this.jQ.outerWidth() / 2) cursor.insLeftOf(this); - else - cursor.insRightOf(this); + else cursor.insRightOf(this); return cursor; - }; + } - latex (){ return this.ctrlSeq || ''; }; - text (){ return this.textTemplate.join(''); }; - mathspeak (_opts?:MathspeakOptions){ return this.mathspeakName || ''; }; - placeCursor () {}; - isEmpty (){ return true; }; -}; + latex() { + return this.ctrlSeq || ''; + } + text() { + return this.textTemplate.join(''); + } + mathspeak(_opts?: MathspeakOptions) { + return this.mathspeakName || ''; + } + placeCursor() {} + isEmpty() { + return true; + } +} class VanillaSymbol extends MQSymbol { - constructor (ch:string, html?:string, mathspeak?:string) { - super(ch, ''+(html || ch)+'', undefined, mathspeak); - }; + constructor(ch: string, html?: string, mathspeak?: string) { + super(ch, '' + (html || ch) + '', undefined, mathspeak); + } } -function bindVanillaSymbol (ch:string, html?:string, mathspeak?:string) { +function bindVanillaSymbol(ch: string, html?: string, mathspeak?: string) { return () => new VanillaSymbol(ch, html, mathspeak); } class BinaryOperator extends MQSymbol { - constructor (ctrlSeq?:string, html?:string, text?:string, mathspeak?:string, treatLikeSymbol?:boolean) { + constructor( + ctrlSeq?: string, + html?: string, + text?: string, + mathspeak?: string, + treatLikeSymbol?: boolean + ) { if (treatLikeSymbol) { - super(ctrlSeq, ''+(html || ctrlSeq)+'', undefined, mathspeak); + super( + ctrlSeq, + '' + (html || ctrlSeq) + '', + undefined, + mathspeak + ); } else { - super(ctrlSeq, ''+html+'', text, mathspeak); + super( + ctrlSeq, + '' + html + '', + text, + mathspeak + ); } - }; -}; -function bindBinaryOperator (ctrlSeq?:string, html?:string, text?:string, mathspeak?:string) { + } +} +function bindBinaryOperator( + ctrlSeq?: string, + html?: string, + text?: string, + mathspeak?: string +) { return () => new BinaryOperator(ctrlSeq, html, text, mathspeak); } @@ -442,79 +526,86 @@ function bindBinaryOperator (ctrlSeq?:string, html?:string, text?:string, mathsp * ancestor operators. */ class MathBlock extends MathElement { - controller?:Controller; + controller?: Controller; - join (methodName:JoinMethod) { - return this.foldChildren('', function(fold, child) { + join(methodName: JoinMethod) { + return this.foldChildren('', function (fold, child) { return fold + child[methodName](); }); - }; - html () { return this.join('html'); }; - latex () { return this.join('latex'); }; - text () { + } + html() { + return this.join('html'); + } + latex() { + return this.join('latex'); + } + text() { var endsL = this.ends[L]; var endsR = this.ends[R]; - return (endsL === endsR && endsL !== 0) ? - endsL.text() : - this.join('text') - ; - }; - mathspeak () { + return endsL === endsR && endsL !== 0 ? endsL.text() : this.join('text'); + } + mathspeak() { var tempOp = ''; - var autoOps:CursorOptions['autoOperatorNames'] = {}; + var autoOps: CursorOptions['autoOperatorNames'] = {}; if (this.controller) autoOps = this.controller.options.autoOperatorNames; - return this.foldChildren([], function(speechArray, cmd) { - if (cmd.isPartOfOperator) { - tempOp += cmd.mathspeak(); - } else { - if(tempOp!=='') { - if(autoOps._maxLength! > 0) { - var x = autoOps[tempOp.toLowerCase()]; - if(typeof x === 'string') tempOp = x; + return ( + this.foldChildren([], function (speechArray, cmd) { + if (cmd.isPartOfOperator) { + tempOp += cmd.mathspeak(); + } else { + if (tempOp !== '') { + if (autoOps._maxLength! > 0) { + var x = autoOps[tempOp.toLowerCase()]; + if (typeof x === 'string') tempOp = x; + } + speechArray.push(tempOp + ' '); + tempOp = ''; } - speechArray.push(tempOp+' '); - tempOp = ''; - } - var mathspeakText = cmd.mathspeak(); - var cmdText = cmd.ctrlSeq; - if ( - isNaN(cmdText as any) && // TODO - revisit this to improve the isNumber() check - cmdText !== '.' && - (!cmd.parent || !cmd.parent.parent || !cmd.parent.parent.isTextBlock()) - ) { - mathspeakText = ' ' + mathspeakText + ' '; + var mathspeakText = cmd.mathspeak(); + var cmdText = cmd.ctrlSeq; + if ( + isNaN(cmdText as any) && // TODO - revisit this to improve the isNumber() check + cmdText !== '.' && + (!cmd.parent || + !cmd.parent.parent || + !cmd.parent.parent.isTextBlock()) + ) { + mathspeakText = ' ' + mathspeakText + ' '; + } + speechArray.push(mathspeakText); } - speechArray.push(mathspeakText); - } - return speechArray; - }) - .join('') - .replace(/ +(?= )/g,'') - // For Apple devices in particular, split out digits after a decimal point so they aren't read aloud as whole words. - // Not doing so makes 123.456 potentially spoken as "one hundred twenty-three point four hundred fifty-six." - // Instead, add spaces so it is spoken as "one hundred twenty-three point four five six." - .replace(/(\.)([0-9]+)/g, function(_match, p1, p2) { - return p1 + p2.split('').join(' ').trim(); - }); - }; + return speechArray; + }) + .join('') + .replace(/ +(?= )/g, '') + // For Apple devices in particular, split out digits after a decimal point so they aren't read aloud as whole words. + // Not doing so makes 123.456 potentially spoken as "one hundred twenty-three point four hundred fifty-six." + // Instead, add spaces so it is spoken as "one hundred twenty-three point four five six." + .replace(/(\.)([0-9]+)/g, function (_match, p1, p2) { + return p1 + p2.split('').join(' ').trim(); + }) + ); + } ariaLabel = 'block'; - keystroke (key:string, e:KeyboardEvent, ctrlr:Controller) { - if (ctrlr.options.spaceBehavesLikeTab - && (key === 'Spacebar' || key === 'Shift-Spacebar')) { + keystroke(key: string, e: KeyboardEvent, ctrlr: Controller) { + if ( + ctrlr.options.spaceBehavesLikeTab && + (key === 'Spacebar' || key === 'Shift-Spacebar') + ) { e.preventDefault(); ctrlr.escapeDir(key === 'Shift-Spacebar' ? L : R, key, e); return; } return super.keystroke(key, e, ctrlr); - }; + } // editability methods: called by the cursor for editing, cursor movements, // and selection of the MathQuill tree, these all take in a direction and // the cursor - moveOutOf (dir:Direction, cursor:Cursor, updown?:'up'|'down') { - var updownInto:NodeRef | undefined; + moveOutOf(dir: Direction, cursor: Cursor, updown?: 'up' | 'down') { + var updownInto: NodeRef | undefined; if (updown === 'up') { updownInto = this.parent.upInto; } else if (updown === 'down') { @@ -525,19 +616,18 @@ class MathBlock extends MathElement { const otherDir = -dir as Direction; cursor.insAtDirEnd(otherDir, this[dir] as MQNode); cursor.controller.aria.queueDirEndOf(otherDir).queue(cursor.parent, true); - } - else { + } else { cursor.insDirOf(dir, this.parent); cursor.controller.aria.queueDirOf(dir).queue(this.parent); } - }; - selectOutOf (dir:Direction, cursor:Cursor) { + } + selectOutOf(dir: Direction, cursor: Cursor) { cursor.insDirOf(dir, this.parent); - }; - deleteOutOf (_dir:Direction, cursor:Cursor) { + } + deleteOutOf(_dir: Direction, cursor: Cursor) { cursor.unwrapGramp(); - }; - seek (pageX:number, cursor:Cursor) { + } + seek(pageX: number, cursor: Cursor) { var node = this.ends[R]; if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) { return cursor.insAtRightEnd(this); @@ -547,31 +637,29 @@ class MathBlock extends MathElement { if (pageX < endsL.jQ.offset().left) return cursor.insAtLeftEnd(this); while (pageX < node.jQ.offset().left) node = node[L] as MQNode; return node.seek(pageX, cursor); - }; - chToCmd (ch:string, options:CursorOptions) { + } + chToCmd(ch: string, options: CursorOptions) { var cons; // exclude f because it gets a dedicated command with more spacing - if (ch.match(/^[a-eg-zA-Z]$/)) - return new Letter(ch); - else if (/^\d$/.test(ch)) - return new Digit(ch); + if (ch.match(/^[a-eg-zA-Z]$/)) return new Letter(ch); + else if (/^\d$/.test(ch)) return new Digit(ch); else if (options && options.typingSlashWritesDivisionSymbol && ch === '/') return (LatexCmds as LatexCmdsSingleCharBuilder)['÷'](ch); else if (options && options.typingAsteriskWritesTimesSymbol && ch === '*') return (LatexCmds as LatexCmdsSingleCharBuilder)['×'](ch); else if (options && options.typingPercentWritesPercentOf && ch === '%') return (LatexCmds as LatexCmdsSingleCharBuilder).percentof(ch); - else if (cons = (CharCmds as CharCmdsAny)[ch] || (LatexCmds as LatexCmdsAny)[ch]) { + else if ( + (cons = (CharCmds as CharCmdsAny)[ch] || (LatexCmds as LatexCmdsAny)[ch]) + ) { if (cons.constructor) { return new cons(ch); } else { return cons(ch); } - } - else - return new VanillaSymbol(ch); - }; - write (cursor:Cursor, ch:string) { + } else return new VanillaSymbol(ch); + } + write(cursor: Cursor, ch: string) { var cmd = this.chToCmd(ch, cursor.options); if (cursor.selection) cmd.replaces(cursor.replaceSelection()); if (!cursor.isTooDeep()) { @@ -583,17 +671,21 @@ class MathBlock extends MathElement { cursor.controller.aria.alert(cmd.mathspeak({ createdLeftOf: cursor })); } } - }; - - writeLatex (cursor:Cursor, latex:string) { + } + writeLatex(cursor: Cursor, latex: string) { var all = Parser.all; var eof = Parser.eof; - var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex); + var block = latexMathParser + .skip(eof) + .or(all.result(false)) + .parse(latex); if (block && !block.isEmpty() && block.prepareInsertionAt(cursor)) { - block.children().adopt(cursor.parent, cursor[L] as NodeRef, cursor[R] as NodeRef); // TODO - masking undefined. should be 0 + block + .children() + .adopt(cursor.parent, cursor[L] as NodeRef, cursor[R] as NodeRef); // TODO - masking undefined. should be 0 var jQ = block.jQize(); jQ.insertBefore(cursor.jQ); cursor[L] = block.ends[R]; @@ -609,32 +701,35 @@ class MathBlock extends MathElement { return undefined; }); } - }; + } - focus () { + focus() { this.jQ.addClass('mq-hasCursor'); this.jQ.removeClass('mq-empty'); return this; - }; - blur (cursor:Cursor) { + } + blur(cursor: Cursor) { this.jQ.removeClass('mq-hasCursor'); if (this.isEmpty()) { this.jQ.addClass('mq-empty'); - if (cursor && this.isQuietEmptyDelimiter(cursor.options.quietEmptyDelimiters)) { + if ( + cursor && + this.isQuietEmptyDelimiter(cursor.options.quietEmptyDelimiters) + ) { this.jQ.addClass('mq-quiet-delimiter'); } } return this; - }; + } } Options.prototype.mouseEvents = true; -API.StaticMath = function(APIClasses:APIClasses) { +API.StaticMath = function (APIClasses: APIClasses) { return class StaticMath extends APIClasses.AbstractMathQuill { static RootBlock = MathBlock; - __mathquillify (opts:CursorOptions, _interfaceVersion:number) { + __mathquillify(opts: CursorOptions, _interfaceVersion: number) { this.config(opts); super.__mathquillify('mq-math-mode'); if (this.__options.mouseEvents) { @@ -642,66 +737,66 @@ API.StaticMath = function(APIClasses:APIClasses) { this.__controller.staticMathTextareaEvents(); } return this; - }; - constructor (el:MQNode) { + } + constructor(el: MQNode) { super(el); - var innerFields = this.innerFields = []; - this.__controller.root.postOrder(function (node:MQNode) { + var innerFields = (this.innerFields = []); + this.__controller.root.postOrder(function (node: MQNode) { node.registerInnerField(innerFields, APIClasses.InnerMathField); }); - }; - latex () { + } + latex() { var returned = super.latex.apply(this, arguments); if (arguments.length > 0) { - var innerFields = this.innerFields = []; - this.__controller.root.postOrder(function (node:MQNode) { + var innerFields = (this.innerFields = []); + this.__controller.root.postOrder(function (node: MQNode) { node.registerInnerField(innerFields, APIClasses.InnerMathField); }); // Force an ARIA label update to remain in sync with the new LaTeX value. this.__controller.updateMathspeak(); } return returned; - }; - setAriaLabel (ariaLabel:string) { + } + setAriaLabel(ariaLabel: string) { this.__controller.setAriaLabel(ariaLabel); return this; - }; - getAriaLabel () { + } + getAriaLabel() { return this.__controller.getAriaLabel(); - }; + } }; }; class RootMathBlock extends MathBlock {} RootBlockMixin(RootMathBlock.prototype); // adds methods to RootMathBlock -API.MathField = function(APIClasses:APIClasses) { +API.MathField = function (APIClasses: APIClasses) { return class MathField extends APIClasses.EditableField { static RootBlock = RootMathBlock; - __mathquillify (opts:CursorOptions, interfaceVersion:number) { + __mathquillify(opts: CursorOptions, interfaceVersion: number) { this.config(opts); if (interfaceVersion > 1) this.__controller.root.reflow = noop; super.__mathquillify('mq-editable-field mq-math-mode'); delete this.__controller.root.reflow; return this; - }; + } }; }; -API.InnerMathField = function(APIClasses:APIClasses) { +API.InnerMathField = function (APIClasses: APIClasses) { return class extends APIClasses.MathField { - makeStatic () { + makeStatic() { this.__controller.editable = false; this.__controller.root.blur(); this.__controller.unbindEditablesEvents(); this.__controller.container.removeClass('mq-editable-field'); - }; - makeEditable () { + } + makeEditable() { this.__controller.editable = true; this.__controller.editablesTextareaEvents(); this.__controller.cursor.insAtRightEnd(this.__controller.root); this.__controller.container.addClass('mq-editable-field'); - }; + } }; }; diff --git a/src/commands/math/LatexCommandInput.ts b/src/commands/math/LatexCommandInput.ts index 528d57743..8ee6f5e14 100644 --- a/src/commands/math/LatexCommandInput.ts +++ b/src/commands/math/LatexCommandInput.ts @@ -4,41 +4,41 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { ctrlSeq = '\\'; - _replacedFragment?:Fragment; + _replacedFragment?: Fragment; - replaces (replacedFragment:Fragment) { + replaces(replacedFragment: Fragment) { this._replacedFragment = replacedFragment.disown(); - this.isEmpty = function() { return false; }; - }; - htmlTemplate = '\\&0'; + this.isEmpty = function () { + return false; + }; + } + htmlTemplate = + '\\&0'; textTemplate = ['\\']; - createBlocks () { + createBlocks() { super.createBlocks(); const endsL = this.ends[L] as MQNode; - endsL.focus = function() { + endsL.focus = function () { this.parent.jQ.addClass('mq-hasCursor'); - if (this.isEmpty()) - this.parent.jQ.removeClass('mq-empty'); + if (this.isEmpty()) this.parent.jQ.removeClass('mq-empty'); return this; }; - endsL.blur = function() { + endsL.blur = function () { this.parent.jQ.removeClass('mq-hasCursor'); - if (this.isEmpty()) - this.parent.jQ.addClass('mq-empty'); + if (this.isEmpty()) this.parent.jQ.addClass('mq-empty'); return this; }; - endsL.write = function(cursor, ch) { + endsL.write = function (cursor, ch) { cursor.show().deleteSelection(); if (ch.match(/[a-z]/i)) { new VanillaSymbol(ch).createLeftOf(cursor); // TODO needs tests cursor.controller.aria.alert(ch); - } - else { + } else { var cmd = (this.parent as LatexCommandInput).renderCommand(cursor); // TODO needs tests cursor.controller.aria.queue(cmd.mathspeak({ createdLeftOf: cursor })); @@ -48,9 +48,11 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { }; var originalKeystroke = endsL.keystroke; - endsL.keystroke = function(key, e, ctrlr) { + endsL.keystroke = function (key, e, ctrlr) { if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') { - var cmd = (this.parent as LatexCommandInput).renderCommand(ctrlr.cursor); + var cmd = (this.parent as LatexCommandInput).renderCommand( + ctrlr.cursor + ); // TODO needs tests ctrlr.aria.alert(cmd.mathspeak({ createdLeftOf: ctrlr.cursor })); e.preventDefault(); @@ -59,28 +61,31 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { return originalKeystroke.call(this, key, e, ctrlr); }; - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { super.createLeftOf(cursor); if (this._replacedFragment) { var el = this.jQ[0]; - this.jQ = - this._replacedFragment.jQ.addClass('mq-blur').bind( + this.jQ = this._replacedFragment.jQ + .addClass('mq-blur') + .bind( 'mousedown mousemove', //FIXME: is monkey-patching the mousedown and mousemove handlers the right way to do this? - function(e) { + function (e) { // TODO - overwritting e.target - (e as any).target = el + (e as any).target = el; $(el).trigger(e); return false; } - ).insertBefore(this.jQ).add(this.jQ); + ) + .insertBefore(this.jQ) + .add(this.jQ); } - }; - latex () { + } + latex() { return '\\' + (this.ends[L] as MQNode).latex() + ' '; - }; - renderCommand (cursor:Cursor) { + } + renderCommand(cursor: Cursor) { this.jQ = this.jQ.last(); this.remove(); if (this[R]) { @@ -94,17 +99,17 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { var cmd = LatexCmds[latex]; if (cmd) { - let node:MQNode; + let node: MQNode; if (isMQNodeClass(cmd)) { node = new (cmd as typeof TempSingleCharNode)(latex); } else { node = cmd(latex); } - if (this._replacedFragment) (node as MathCommand).replaces(this._replacedFragment); + if (this._replacedFragment) + (node as MathCommand).replaces(this._replacedFragment); node.createLeftOf(cursor); return node; - } - else { + } else { const node = new TextBlock(); node.replaces(latex); node.createLeftOf(cursor); @@ -114,6 +119,5 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { } return node; } - }; + } }; - diff --git a/src/commands/math/advancedSymbols.ts b/src/commands/math/advancedSymbols.ts index 3c430a21e..0a46ce78f 100644 --- a/src/commands/math/advancedSymbols.ts +++ b/src/commands/math/advancedSymbols.ts @@ -3,121 +3,208 @@ ***********************************/ LatexCmds.notin = -LatexCmds.cong = -LatexCmds.equiv = -LatexCmds.oplus = -LatexCmds.otimes = (latex:string) => new BinaryOperator('\\'+latex+' ', '&'+latex+';'); - -LatexCmds['∗'] = LatexCmds.ast = LatexCmds.star = LatexCmds.loast = LatexCmds.lowast = - bindBinaryOperator('\\ast ','∗', 'low asterisk'); -LatexCmds.therefor = LatexCmds.therefore = - bindBinaryOperator('\\therefore ','∴', 'therefore'); - -LatexCmds.cuz = // l33t -LatexCmds.because = bindBinaryOperator('\\because ','∵', 'because'); - -LatexCmds.prop = LatexCmds.propto = bindBinaryOperator('\\propto ','∝', 'proportional to'); - -LatexCmds['≈'] = LatexCmds.asymp = LatexCmds.approx = bindBinaryOperator('\\approx ','≈', 'approximately equal to'); - -LatexCmds.isin = LatexCmds['in'] = bindBinaryOperator('\\in ','∈', 'is in'); - -LatexCmds.ni = LatexCmds.contains = bindBinaryOperator('\\ni ','∋', 'is not in'); - -LatexCmds.notni = LatexCmds.niton = LatexCmds.notcontains = LatexCmds.doesnotcontain = - bindBinaryOperator('\\not\\ni ','∌', 'does not contain'); - -LatexCmds.sub = LatexCmds.subset = bindBinaryOperator('\\subset ','⊂', 'subset'); - -LatexCmds.sup = LatexCmds.supset = LatexCmds.superset = - bindBinaryOperator('\\supset ','⊃', 'superset'); - -LatexCmds.nsub = LatexCmds.notsub = -LatexCmds.nsubset = LatexCmds.notsubset = - bindBinaryOperator('\\not\\subset ','⊄', 'not a subset'); - -LatexCmds.nsup = LatexCmds.notsup = -LatexCmds.nsupset = LatexCmds.notsupset = -LatexCmds.nsuperset = LatexCmds.notsuperset = - bindBinaryOperator('\\not\\supset ','⊅', 'not a superset'); - -LatexCmds.sube = LatexCmds.subeq = LatexCmds.subsete = LatexCmds.subseteq = - bindBinaryOperator('\\subseteq ','⊆', 'subset or equal to'); - -LatexCmds.supe = LatexCmds.supeq = -LatexCmds.supsete = LatexCmds.supseteq = -LatexCmds.supersete = LatexCmds.superseteq = - bindBinaryOperator('\\supseteq ','⊇', 'superset or equal to'); - -LatexCmds.nsube = LatexCmds.nsubeq = -LatexCmds.notsube = LatexCmds.notsubeq = -LatexCmds.nsubsete = LatexCmds.nsubseteq = -LatexCmds.notsubsete = LatexCmds.notsubseteq = - bindBinaryOperator('\\not\\subseteq ','⊈', 'not subset or equal to'); - -LatexCmds.nsupe = LatexCmds.nsupeq = -LatexCmds.notsupe = LatexCmds.notsupeq = -LatexCmds.nsupsete = LatexCmds.nsupseteq = -LatexCmds.notsupsete = LatexCmds.notsupseteq = -LatexCmds.nsupersete = LatexCmds.nsuperseteq = -LatexCmds.notsupersete = LatexCmds.notsuperseteq = - bindBinaryOperator('\\not\\supseteq ','⊉', 'not superset or equal to'); + LatexCmds.cong = + LatexCmds.equiv = + LatexCmds.oplus = + LatexCmds.otimes = + (latex: string) => + new BinaryOperator('\\' + latex + ' ', '&' + latex + ';'); + +LatexCmds['∗'] = + LatexCmds.ast = + LatexCmds.star = + LatexCmds.loast = + LatexCmds.lowast = + bindBinaryOperator('\\ast ', '∗', 'low asterisk'); +LatexCmds.therefor = LatexCmds.therefore = bindBinaryOperator( + '\\therefore ', + '∴', + 'therefore' +); + +LatexCmds.cuz = LatexCmds.because = bindBinaryOperator( + // l33t + '\\because ', + '∵', + 'because' +); + +LatexCmds.prop = LatexCmds.propto = bindBinaryOperator( + '\\propto ', + '∝', + 'proportional to' +); + +LatexCmds['≈'] = + LatexCmds.asymp = + LatexCmds.approx = + bindBinaryOperator('\\approx ', '≈', 'approximately equal to'); + +LatexCmds.isin = LatexCmds['in'] = bindBinaryOperator( + '\\in ', + '∈', + 'is in' +); + +LatexCmds.ni = LatexCmds.contains = bindBinaryOperator( + '\\ni ', + '∋', + 'is not in' +); + +LatexCmds.notni = + LatexCmds.niton = + LatexCmds.notcontains = + LatexCmds.doesnotcontain = + bindBinaryOperator('\\not\\ni ', '∌', 'does not contain'); + +LatexCmds.sub = LatexCmds.subset = bindBinaryOperator( + '\\subset ', + '⊂', + 'subset' +); + +LatexCmds.sup = + LatexCmds.supset = + LatexCmds.superset = + bindBinaryOperator('\\supset ', '⊃', 'superset'); + +LatexCmds.nsub = + LatexCmds.notsub = + LatexCmds.nsubset = + LatexCmds.notsubset = + bindBinaryOperator('\\not\\subset ', '⊄', 'not a subset'); + +LatexCmds.nsup = + LatexCmds.notsup = + LatexCmds.nsupset = + LatexCmds.notsupset = + LatexCmds.nsuperset = + LatexCmds.notsuperset = + bindBinaryOperator('\\not\\supset ', '⊅', 'not a superset'); + +LatexCmds.sube = + LatexCmds.subeq = + LatexCmds.subsete = + LatexCmds.subseteq = + bindBinaryOperator('\\subseteq ', '⊆', 'subset or equal to'); + +LatexCmds.supe = + LatexCmds.supeq = + LatexCmds.supsete = + LatexCmds.supseteq = + LatexCmds.supersete = + LatexCmds.superseteq = + bindBinaryOperator('\\supseteq ', '⊇', 'superset or equal to'); + +LatexCmds.nsube = + LatexCmds.nsubeq = + LatexCmds.notsube = + LatexCmds.notsubeq = + LatexCmds.nsubsete = + LatexCmds.nsubseteq = + LatexCmds.notsubsete = + LatexCmds.notsubseteq = + bindBinaryOperator('\\not\\subseteq ', '⊈', 'not subset or equal to'); + +LatexCmds.nsupe = + LatexCmds.nsupeq = + LatexCmds.notsupe = + LatexCmds.notsupeq = + LatexCmds.nsupsete = + LatexCmds.nsupseteq = + LatexCmds.notsupsete = + LatexCmds.notsupseteq = + LatexCmds.nsupersete = + LatexCmds.nsuperseteq = + LatexCmds.notsupersete = + LatexCmds.notsuperseteq = + bindBinaryOperator( + '\\not\\supseteq ', + '⊉', + 'not superset or equal to' + ); //the canonical sets of numbers LatexCmds.mathbb = class extends MathCommand { - createLeftOf (_cursor:Cursor) {}; - numBlocks () { return 1; }; - parser () { + createLeftOf(_cursor: Cursor) {} + numBlocks() { + return 1; + } + parser() { var string = Parser.string; var regex = Parser.regex; var optWhitespace = Parser.optWhitespace; - return optWhitespace.then(string('{')) - .then(optWhitespace) - .then(regex(/^[NPZQRCH]/)) - .skip(optWhitespace) - .skip(string('}')) - .map(function(c) { - // instantiate the class for the matching char - var cmd = LatexCmds[c]; - if (isMQNodeClass(cmd)) { - return new cmd(); - } else { - return (cmd as MQNodeBuilderNoParam)(); - } - }); - }; + return optWhitespace + .then(string('{')) + .then(optWhitespace) + .then(regex(/^[NPZQRCH]/)) + .skip(optWhitespace) + .skip(string('}')) + .map(function (c) { + // instantiate the class for the matching char + var cmd = LatexCmds[c]; + if (isMQNodeClass(cmd)) { + return new cmd(); + } else { + return (cmd as MQNodeBuilderNoParam)(); + } + }); + } }; -LatexCmds.N = LatexCmds.naturals = LatexCmds.Naturals = - bindVanillaSymbol('\\mathbb{N}','ℕ', 'naturals'); +LatexCmds.N = + LatexCmds.naturals = + LatexCmds.Naturals = + bindVanillaSymbol('\\mathbb{N}', 'ℕ', 'naturals'); LatexCmds.P = -LatexCmds.primes = LatexCmds.Primes = -LatexCmds.projective = LatexCmds.Projective = -LatexCmds.probability = LatexCmds.Probability = - bindVanillaSymbol('\\mathbb{P}','ℙ', 'P'); - -LatexCmds.Z = LatexCmds.integers = LatexCmds.Integers = - bindVanillaSymbol('\\mathbb{Z}','ℤ', 'integers'); - -LatexCmds.Q = LatexCmds.rationals = LatexCmds.Rationals = - bindVanillaSymbol('\\mathbb{Q}','ℚ', 'rationals'); - -LatexCmds.R = LatexCmds.reals = LatexCmds.Reals = - bindVanillaSymbol('\\mathbb{R}','ℝ', 'reals'); + LatexCmds.primes = + LatexCmds.Primes = + LatexCmds.projective = + LatexCmds.Projective = + LatexCmds.probability = + LatexCmds.Probability = + bindVanillaSymbol('\\mathbb{P}', 'ℙ', 'P'); + +LatexCmds.Z = + LatexCmds.integers = + LatexCmds.Integers = + bindVanillaSymbol('\\mathbb{Z}', 'ℤ', 'integers'); + +LatexCmds.Q = + LatexCmds.rationals = + LatexCmds.Rationals = + bindVanillaSymbol('\\mathbb{Q}', 'ℚ', 'rationals'); + +LatexCmds.R = + LatexCmds.reals = + LatexCmds.Reals = + bindVanillaSymbol('\\mathbb{R}', 'ℝ', 'reals'); LatexCmds.C = -LatexCmds.complex = LatexCmds.Complex = -LatexCmds.complexes = LatexCmds.Complexes = -LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane = - bindVanillaSymbol('\\mathbb{C}','ℂ', 'complexes'); - -LatexCmds.H = LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions = - bindVanillaSymbol('\\mathbb{H}','ℍ', 'quaternions'); + LatexCmds.complex = + LatexCmds.Complex = + LatexCmds.complexes = + LatexCmds.Complexes = + LatexCmds.complexplane = + LatexCmds.Complexplane = + LatexCmds.ComplexPlane = + bindVanillaSymbol('\\mathbb{C}', 'ℂ', 'complexes'); + +LatexCmds.H = + LatexCmds.Hamiltonian = + LatexCmds.quaternions = + LatexCmds.Quaternions = + bindVanillaSymbol('\\mathbb{H}', 'ℍ', 'quaternions'); //spacing -LatexCmds.quad = LatexCmds.emsp = bindVanillaSymbol('\\quad ',' ', '4 spaces'); -LatexCmds.qquad = bindVanillaSymbol('\\qquad ',' ', '8 spaces'); +LatexCmds.quad = LatexCmds.emsp = bindVanillaSymbol( + '\\quad ', + ' ', + '4 spaces' +); +LatexCmds.qquad = bindVanillaSymbol('\\qquad ', ' ', '8 spaces'); /* spacing special characters, gonna have to implement this in LatexCommandInput::onText somehow case ',': return VanillaSymbol('\\, ',' ', 'comma'); @@ -131,16 +218,40 @@ case '!': //binary operators LatexCmds.diamond = bindVanillaSymbol('\\diamond ', '◇', 'diamond'); -LatexCmds.bigtriangleup = bindVanillaSymbol('\\bigtriangleup ', '△', 'triangle up'); +LatexCmds.bigtriangleup = bindVanillaSymbol( + '\\bigtriangleup ', + '△', + 'triangle up' +); LatexCmds.ominus = bindVanillaSymbol('\\ominus ', '⊖', 'o minus'); LatexCmds.uplus = bindVanillaSymbol('\\uplus ', '⊎', 'disjoint union'); -LatexCmds.bigtriangledown = bindVanillaSymbol('\\bigtriangledown ', '▽', 'triangle down'); -LatexCmds.sqcap = bindVanillaSymbol('\\sqcap ', '⊓', 'greatest lower bound'); -LatexCmds.triangleleft = bindVanillaSymbol('\\triangleleft ', '⊲', 'triangle left'); +LatexCmds.bigtriangledown = bindVanillaSymbol( + '\\bigtriangledown ', + '▽', + 'triangle down' +); +LatexCmds.sqcap = bindVanillaSymbol( + '\\sqcap ', + '⊓', + 'greatest lower bound' +); +LatexCmds.triangleleft = bindVanillaSymbol( + '\\triangleleft ', + '⊲', + 'triangle left' +); LatexCmds.sqcup = bindVanillaSymbol('\\sqcup ', '⊔', 'least upper bound'); -LatexCmds.triangleright = bindVanillaSymbol('\\triangleright ', '⊳', 'triangle right'); +LatexCmds.triangleright = bindVanillaSymbol( + '\\triangleright ', + '⊳', + 'triangle right' +); //circledot is not a not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details -LatexCmds.odot = LatexCmds.circledot = bindVanillaSymbol('\\odot ', '⊙', 'circle dot'); +LatexCmds.odot = LatexCmds.circledot = bindVanillaSymbol( + '\\odot ', + '⊙', + 'circle dot' +); LatexCmds.bigcirc = bindVanillaSymbol('\\bigcirc ', '◯', 'circle'); LatexCmds.dagger = bindVanillaSymbol('\\dagger ', '†', 'dagger'); LatexCmds.ddagger = bindVanillaSymbol('\\ddagger ', '‡', 'big dagger'); @@ -151,20 +262,56 @@ LatexCmds.amalg = bindVanillaSymbol('\\amalg ', '∐', 'amalgam'); LatexCmds.models = bindVanillaSymbol('\\models ', '⊨', 'models'); LatexCmds.prec = bindVanillaSymbol('\\prec ', '≺', 'precedes'); LatexCmds.succ = bindVanillaSymbol('\\succ ', '≻', 'succeeds'); -LatexCmds.preceq = bindVanillaSymbol('\\preceq ', '≼', 'precedes or equals'); -LatexCmds.succeq = bindVanillaSymbol('\\succeq ', '≽', 'succeeds or equals'); -LatexCmds.simeq = bindVanillaSymbol('\\simeq ', '≃', 'similar or equal to'); +LatexCmds.preceq = bindVanillaSymbol( + '\\preceq ', + '≼', + 'precedes or equals' +); +LatexCmds.succeq = bindVanillaSymbol( + '\\succeq ', + '≽', + 'succeeds or equals' +); +LatexCmds.simeq = bindVanillaSymbol( + '\\simeq ', + '≃', + 'similar or equal to' +); LatexCmds.mid = bindVanillaSymbol('\\mid ', '∣', 'divides'); LatexCmds.ll = bindVanillaSymbol('\\ll ', '≪', 'll'); LatexCmds.gg = bindVanillaSymbol('\\gg ', '≫', 'gg'); -LatexCmds.parallel = bindVanillaSymbol('\\parallel ', '∥', 'parallel with'); -LatexCmds.nparallel = bindVanillaSymbol('\\nparallel ', '∦', 'not parallel with'); +LatexCmds.parallel = bindVanillaSymbol( + '\\parallel ', + '∥', + 'parallel with' +); +LatexCmds.nparallel = bindVanillaSymbol( + '\\nparallel ', + '∦', + 'not parallel with' +); LatexCmds.bowtie = bindVanillaSymbol('\\bowtie ', '⋈', 'bowtie'); -LatexCmds.sqsubset = bindVanillaSymbol('\\sqsubset ', '⊏', 'square subset'); -LatexCmds.sqsupset = bindVanillaSymbol('\\sqsupset ', '⊐', 'square superset'); +LatexCmds.sqsubset = bindVanillaSymbol( + '\\sqsubset ', + '⊏', + 'square subset' +); +LatexCmds.sqsupset = bindVanillaSymbol( + '\\sqsupset ', + '⊐', + 'square superset' +); LatexCmds.smile = bindVanillaSymbol('\\smile ', '⌣', 'smile'); -LatexCmds.sqsubseteq = bindVanillaSymbol('\\sqsubseteq ', '⊑', 'square subset or equal to'); -LatexCmds.sqsupseteq = bindVanillaSymbol('\\sqsupseteq ', '⊒', 'square superset or equal to'); +LatexCmds.sqsubseteq = bindVanillaSymbol( + '\\sqsubseteq ', + '⊑', + 'square subset or equal to' +); +LatexCmds.sqsupseteq = bindVanillaSymbol( + '\\sqsupseteq ', + '⊒', + 'square superset or equal to' +); LatexCmds.doteq = bindVanillaSymbol('\\doteq ', '≐', 'dotted equals'); LatexCmds.frown = bindVanillaSymbol('\\frown ', '⌢', 'frown'); LatexCmds.vdash = bindVanillaSymbol('\\vdash ', '⊦', 'v dash'); @@ -173,25 +320,97 @@ LatexCmds.nless = bindVanillaSymbol('\\nless ', '≮', 'not less than'); LatexCmds.ngtr = bindVanillaSymbol('\\ngtr ', '≯', 'not greater than'); //arrows -LatexCmds.longleftarrow = bindVanillaSymbol('\\longleftarrow ', '←', 'left arrow'); -LatexCmds.longrightarrow = bindVanillaSymbol('\\longrightarrow ', '→', 'right arrow'); -LatexCmds.Longleftarrow = bindVanillaSymbol('\\Longleftarrow ', '⇐', 'left arrow'); -LatexCmds.Longrightarrow = bindVanillaSymbol('\\Longrightarrow ', '⇒', 'right arrow'); -LatexCmds.longleftrightarrow = bindVanillaSymbol('\\longleftrightarrow ', '↔', 'left and right arrow'); -LatexCmds.updownarrow = bindVanillaSymbol('\\updownarrow ', '↕', 'up and down arrow'); -LatexCmds.Longleftrightarrow = bindVanillaSymbol('\\Longleftrightarrow ', '⇔', 'left and right arrow'); -LatexCmds.Updownarrow = bindVanillaSymbol('\\Updownarrow ', '⇕', 'up and down arrow'); +LatexCmds.longleftarrow = bindVanillaSymbol( + '\\longleftarrow ', + '←', + 'left arrow' +); +LatexCmds.longrightarrow = bindVanillaSymbol( + '\\longrightarrow ', + '→', + 'right arrow' +); +LatexCmds.Longleftarrow = bindVanillaSymbol( + '\\Longleftarrow ', + '⇐', + 'left arrow' +); +LatexCmds.Longrightarrow = bindVanillaSymbol( + '\\Longrightarrow ', + '⇒', + 'right arrow' +); +LatexCmds.longleftrightarrow = bindVanillaSymbol( + '\\longleftrightarrow ', + '↔', + 'left and right arrow' +); +LatexCmds.updownarrow = bindVanillaSymbol( + '\\updownarrow ', + '↕', + 'up and down arrow' +); +LatexCmds.Longleftrightarrow = bindVanillaSymbol( + '\\Longleftrightarrow ', + '⇔', + 'left and right arrow' +); +LatexCmds.Updownarrow = bindVanillaSymbol( + '\\Updownarrow ', + '⇕', + 'up and down arrow' +); LatexCmds.mapsto = bindVanillaSymbol('\\mapsto ', '↦', 'maps to'); -LatexCmds.nearrow = bindVanillaSymbol('\\nearrow ', '↗', 'northeast arrow'); -LatexCmds.hookleftarrow = bindVanillaSymbol('\\hookleftarrow ', '↩', 'hook left arrow'); -LatexCmds.hookrightarrow = bindVanillaSymbol('\\hookrightarrow ', '↪', 'hook right arrow'); -LatexCmds.searrow = bindVanillaSymbol('\\searrow ', '↘', 'southeast arrow'); -LatexCmds.leftharpoonup = bindVanillaSymbol('\\leftharpoonup ', '↼', 'left harpoon up'); -LatexCmds.rightharpoonup = bindVanillaSymbol('\\rightharpoonup ', '⇀', 'right harpoon up'); -LatexCmds.swarrow = bindVanillaSymbol('\\swarrow ', '↙', 'southwest arrow'); -LatexCmds.leftharpoondown = bindVanillaSymbol('\\leftharpoondown ', '↽', 'left harpoon down'); -LatexCmds.rightharpoondown = bindVanillaSymbol('\\rightharpoondown ', '⇁', 'right harpoon down'); -LatexCmds.nwarrow = bindVanillaSymbol('\\nwarrow ', '↖', 'northwest arrow'); +LatexCmds.nearrow = bindVanillaSymbol( + '\\nearrow ', + '↗', + 'northeast arrow' +); +LatexCmds.hookleftarrow = bindVanillaSymbol( + '\\hookleftarrow ', + '↩', + 'hook left arrow' +); +LatexCmds.hookrightarrow = bindVanillaSymbol( + '\\hookrightarrow ', + '↪', + 'hook right arrow' +); +LatexCmds.searrow = bindVanillaSymbol( + '\\searrow ', + '↘', + 'southeast arrow' +); +LatexCmds.leftharpoonup = bindVanillaSymbol( + '\\leftharpoonup ', + '↼', + 'left harpoon up' +); +LatexCmds.rightharpoonup = bindVanillaSymbol( + '\\rightharpoonup ', + '⇀', + 'right harpoon up' +); +LatexCmds.swarrow = bindVanillaSymbol( + '\\swarrow ', + '↙', + 'southwest arrow' +); +LatexCmds.leftharpoondown = bindVanillaSymbol( + '\\leftharpoondown ', + '↽', + 'left harpoon down' +); +LatexCmds.rightharpoondown = bindVanillaSymbol( + '\\rightharpoondown ', + '⇁', + 'right harpoon down' +); +LatexCmds.nwarrow = bindVanillaSymbol( + '\\nwarrow ', + '↖', + 'northwest arrow' +); //Misc LatexCmds.ldots = bindVanillaSymbol('\\ldots ', '…', 'l dots'); @@ -208,22 +427,46 @@ LatexCmds.sharp = bindVanillaSymbol('\\sharp ', '♯', 'sharp'); LatexCmds.wp = bindVanillaSymbol('\\wp ', '℘', 'wp'); LatexCmds.bot = bindVanillaSymbol('\\bot ', '⊥', 'bot'); LatexCmds.clubsuit = bindVanillaSymbol('\\clubsuit ', '♣', 'club suit'); -LatexCmds.diamondsuit = bindVanillaSymbol('\\diamondsuit ', '♢', 'diamond suit'); -LatexCmds.heartsuit = bindVanillaSymbol('\\heartsuit ', '♡', 'heart suit'); -LatexCmds.spadesuit = bindVanillaSymbol('\\spadesuit ', '♠', 'spade suit'); +LatexCmds.diamondsuit = bindVanillaSymbol( + '\\diamondsuit ', + '♢', + 'diamond suit' +); +LatexCmds.heartsuit = bindVanillaSymbol( + '\\heartsuit ', + '♡', + 'heart suit' +); +LatexCmds.spadesuit = bindVanillaSymbol( + '\\spadesuit ', + '♠', + 'spade suit' +); //not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details -LatexCmds.parallelogram = bindVanillaSymbol('\\parallelogram ', '▱', 'parallelogram'); +LatexCmds.parallelogram = bindVanillaSymbol( + '\\parallelogram ', + '▱', + 'parallelogram' +); LatexCmds.square = bindVanillaSymbol('\\square ', '⬜', 'square'); //variable-sized LatexCmds.oint = bindVanillaSymbol('\\oint ', '∮', 'o int'); LatexCmds.bigcap = bindVanillaSymbol('\\bigcap ', '∩', 'big cap'); LatexCmds.bigcup = bindVanillaSymbol('\\bigcup ', '∪', 'big cup'); -LatexCmds.bigsqcup = bindVanillaSymbol('\\bigsqcup ', '⊔', 'big square cup'); +LatexCmds.bigsqcup = bindVanillaSymbol( + '\\bigsqcup ', + '⊔', + 'big square cup' +); LatexCmds.bigvee = bindVanillaSymbol('\\bigvee ', '∨', 'big vee'); LatexCmds.bigwedge = bindVanillaSymbol('\\bigwedge ', '∧', 'big wedge'); LatexCmds.bigodot = bindVanillaSymbol('\\bigodot ', '⊙', 'big o dot'); -LatexCmds.bigotimes = bindVanillaSymbol('\\bigotimes ', '⊗', 'big o times'); +LatexCmds.bigotimes = bindVanillaSymbol( + '\\bigotimes ', + '⊗', + 'big o times' +); LatexCmds.bigoplus = bindVanillaSymbol('\\bigoplus ', '⊕', 'big o plus'); LatexCmds.biguplus = bindVanillaSymbol('\\biguplus ', '⊎', 'big u plus'); @@ -232,108 +475,223 @@ LatexCmds.lfloor = bindVanillaSymbol('\\lfloor ', '⌊', 'left floor'); LatexCmds.rfloor = bindVanillaSymbol('\\rfloor ', '⌋', 'right floor'); LatexCmds.lceil = bindVanillaSymbol('\\lceil ', '⌈', 'left ceiling'); LatexCmds.rceil = bindVanillaSymbol('\\rceil ', '⌉', 'right ceiling'); -LatexCmds.opencurlybrace = LatexCmds.lbrace = bindVanillaSymbol('\\lbrace ', '{', 'left brace'); -LatexCmds.closecurlybrace = LatexCmds.rbrace = bindVanillaSymbol('\\rbrace ', '}', 'right brace'); +LatexCmds.opencurlybrace = LatexCmds.lbrace = bindVanillaSymbol( + '\\lbrace ', + '{', + 'left brace' +); +LatexCmds.closecurlybrace = LatexCmds.rbrace = bindVanillaSymbol( + '\\rbrace ', + '}', + 'right brace' +); LatexCmds.lbrack = bindVanillaSymbol('[', 'left bracket'); LatexCmds.rbrack = bindVanillaSymbol(']', 'right bracket'); //various symbols LatexCmds.slash = bindVanillaSymbol('/', 'slash'); LatexCmds.vert = bindVanillaSymbol('|', 'vertical bar'); -LatexCmds.perp = LatexCmds.perpendicular = bindVanillaSymbol('\\perp ','⊥', 'perpendicular'); -LatexCmds.nabla = LatexCmds.del = bindVanillaSymbol('\\nabla ','∇'); -LatexCmds.hbar = bindVanillaSymbol('\\hbar ','ℏ', 'horizontal bar'); - -LatexCmds.AA = LatexCmds.Angstrom = LatexCmds.angstrom = - bindVanillaSymbol('\\text\\AA ','Å', 'AA'); - -LatexCmds.ring = LatexCmds.circ = LatexCmds.circle = - bindVanillaSymbol('\\circ ','∘', 'circle'); - -LatexCmds.bull = LatexCmds.bullet = bindVanillaSymbol('\\bullet ','•', 'bullet'); - -LatexCmds.setminus = LatexCmds.smallsetminus = - bindVanillaSymbol('\\setminus ','∖', 'set minus'); +LatexCmds.perp = LatexCmds.perpendicular = bindVanillaSymbol( + '\\perp ', + '⊥', + 'perpendicular' +); +LatexCmds.nabla = LatexCmds.del = bindVanillaSymbol('\\nabla ', '∇'); +LatexCmds.hbar = bindVanillaSymbol('\\hbar ', 'ℏ', 'horizontal bar'); + +LatexCmds.AA = + LatexCmds.Angstrom = + LatexCmds.angstrom = + bindVanillaSymbol('\\text\\AA ', 'Å', 'AA'); + +LatexCmds.ring = + LatexCmds.circ = + LatexCmds.circle = + bindVanillaSymbol('\\circ ', '∘', 'circle'); + +LatexCmds.bull = LatexCmds.bullet = bindVanillaSymbol( + '\\bullet ', + '•', + 'bullet' +); + +LatexCmds.setminus = LatexCmds.smallsetminus = bindVanillaSymbol( + '\\setminus ', + '∖', + 'set minus' +); LatexCmds.not = //bind(MQSymbol,'\\not ','/', 'not'); -LatexCmds['¬'] = LatexCmds.neg = bindVanillaSymbol('\\neg ','¬', 'not'); - -LatexCmds['…'] = LatexCmds.dots = LatexCmds.ellip = LatexCmds.hellip = -LatexCmds.ellipsis = LatexCmds.hellipsis = - bindVanillaSymbol('\\dots ','…', 'ellipsis'); + LatexCmds['¬'] = + LatexCmds.neg = + bindVanillaSymbol('\\neg ', '¬', 'not'); + +LatexCmds['…'] = + LatexCmds.dots = + LatexCmds.ellip = + LatexCmds.hellip = + LatexCmds.ellipsis = + LatexCmds.hellipsis = + bindVanillaSymbol('\\dots ', '…', 'ellipsis'); LatexCmds.converges = -LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow = - bindVanillaSymbol('\\downarrow ','↓', 'converges with'); - -LatexCmds.dArr = LatexCmds.dnArr = LatexCmds.dnArrow = LatexCmds.Downarrow = - bindVanillaSymbol('\\Downarrow ','⇓', 'down arrow'); - -LatexCmds.diverges = LatexCmds.uarr = LatexCmds.uparrow = - bindVanillaSymbol('\\uparrow ','↑', 'diverges from'); - -LatexCmds.uArr = LatexCmds.Uparrow = bindVanillaSymbol('\\Uparrow ','⇑', 'up arrow'); - -LatexCmds.rarr = LatexCmds.rightarrow = bindVanillaSymbol('\\rightarrow ','→', 'right arrow'); - -LatexCmds.implies = bindBinaryOperator('\\Rightarrow ','⇒', 'implies'); - -LatexCmds.rArr = LatexCmds.Rightarrow = bindVanillaSymbol('\\Rightarrow ','⇒', 'right arrow'); - -LatexCmds.gets = bindBinaryOperator('\\gets ','←', 'gets'); - -LatexCmds.larr = LatexCmds.leftarrow = bindVanillaSymbol('\\leftarrow ','←', 'left arrow'); - -LatexCmds.impliedby = bindBinaryOperator('\\Leftarrow ','⇐', 'implied by'); - -LatexCmds.lArr = LatexCmds.Leftarrow = bindVanillaSymbol('\\Leftarrow ','⇐', 'left arrow'); - -LatexCmds.harr = LatexCmds.lrarr = LatexCmds.leftrightarrow = - bindVanillaSymbol('\\leftrightarrow ','↔', 'left and right arrow'); - -LatexCmds.iff = bindBinaryOperator('\\Leftrightarrow ','⇔', 'if and only if'); - -LatexCmds.hArr = LatexCmds.lrArr = LatexCmds.Leftrightarrow = - bindVanillaSymbol('\\Leftrightarrow ','⇔', 'left and right arrow'); - -LatexCmds.Re = LatexCmds.Real = LatexCmds.real = bindVanillaSymbol('\\Re ','ℜ', 'real'); - -LatexCmds.Im = LatexCmds.imag = -LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary = - bindVanillaSymbol('\\Im ','ℑ', 'imaginary'); - -LatexCmds.part = LatexCmds.partial = bindVanillaSymbol('\\partial ','∂', 'partial'); - -LatexCmds.pounds = bindVanillaSymbol('\\pounds ','£'); - -LatexCmds.alef = LatexCmds.alefsym = LatexCmds.aleph = LatexCmds.alephsym = - bindVanillaSymbol('\\aleph ','ℵ', 'alef sym'); + LatexCmds.darr = + LatexCmds.dnarr = + LatexCmds.dnarrow = + LatexCmds.downarrow = + bindVanillaSymbol('\\downarrow ', '↓', 'converges with'); + +LatexCmds.dArr = + LatexCmds.dnArr = + LatexCmds.dnArrow = + LatexCmds.Downarrow = + bindVanillaSymbol('\\Downarrow ', '⇓', 'down arrow'); + +LatexCmds.diverges = + LatexCmds.uarr = + LatexCmds.uparrow = + bindVanillaSymbol('\\uparrow ', '↑', 'diverges from'); + +LatexCmds.uArr = LatexCmds.Uparrow = bindVanillaSymbol( + '\\Uparrow ', + '⇑', + 'up arrow' +); + +LatexCmds.rarr = LatexCmds.rightarrow = bindVanillaSymbol( + '\\rightarrow ', + '→', + 'right arrow' +); + +LatexCmds.implies = bindBinaryOperator('\\Rightarrow ', '⇒', 'implies'); + +LatexCmds.rArr = LatexCmds.Rightarrow = bindVanillaSymbol( + '\\Rightarrow ', + '⇒', + 'right arrow' +); + +LatexCmds.gets = bindBinaryOperator('\\gets ', '←', 'gets'); + +LatexCmds.larr = LatexCmds.leftarrow = bindVanillaSymbol( + '\\leftarrow ', + '←', + 'left arrow' +); + +LatexCmds.impliedby = bindBinaryOperator( + '\\Leftarrow ', + '⇐', + 'implied by' +); + +LatexCmds.lArr = LatexCmds.Leftarrow = bindVanillaSymbol( + '\\Leftarrow ', + '⇐', + 'left arrow' +); + +LatexCmds.harr = + LatexCmds.lrarr = + LatexCmds.leftrightarrow = + bindVanillaSymbol('\\leftrightarrow ', '↔', 'left and right arrow'); + +LatexCmds.iff = bindBinaryOperator( + '\\Leftrightarrow ', + '⇔', + 'if and only if' +); + +LatexCmds.hArr = + LatexCmds.lrArr = + LatexCmds.Leftrightarrow = + bindVanillaSymbol('\\Leftrightarrow ', '⇔', 'left and right arrow'); + +LatexCmds.Re = + LatexCmds.Real = + LatexCmds.real = + bindVanillaSymbol('\\Re ', 'ℜ', 'real'); + +LatexCmds.Im = + LatexCmds.imag = + LatexCmds.image = + LatexCmds.imagin = + LatexCmds.imaginary = + LatexCmds.Imaginary = + bindVanillaSymbol('\\Im ', 'ℑ', 'imaginary'); + +LatexCmds.part = LatexCmds.partial = bindVanillaSymbol( + '\\partial ', + '∂', + 'partial' +); + +LatexCmds.pounds = bindVanillaSymbol('\\pounds ', '£'); + +LatexCmds.alef = + LatexCmds.alefsym = + LatexCmds.aleph = + LatexCmds.alephsym = + bindVanillaSymbol('\\aleph ', 'ℵ', 'alef sym'); LatexCmds.xist = //LOL -LatexCmds.xists = LatexCmds.exist = LatexCmds.exists = - bindVanillaSymbol('\\exists ','∃', 'there exists at least 1'); - -LatexCmds.nexists = LatexCmds.nexist = - bindVanillaSymbol('\\nexists ', '∄', 'there is no'); - -LatexCmds.and = LatexCmds.land = LatexCmds.wedge = - bindBinaryOperator('\\wedge ','∧', 'and'); - -LatexCmds.or = LatexCmds.lor = LatexCmds.vee = bindBinaryOperator('\\vee ','∨', 'or'); - -LatexCmds.o = LatexCmds.O = -LatexCmds.empty = LatexCmds.emptyset = -LatexCmds.oslash = LatexCmds.Oslash = -LatexCmds.nothing = LatexCmds.varnothing = - bindBinaryOperator('\\varnothing ','∅', 'nothing'); - -LatexCmds.cup = LatexCmds.union = bindBinaryOperator('\\cup ','∪', 'union'); - -LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection = - bindBinaryOperator('\\cap ','∩', 'intersection'); + LatexCmds.xists = + LatexCmds.exist = + LatexCmds.exists = + bindVanillaSymbol('\\exists ', '∃', 'there exists at least 1'); + +LatexCmds.nexists = LatexCmds.nexist = bindVanillaSymbol( + '\\nexists ', + '∄', + 'there is no' +); + +LatexCmds.and = + LatexCmds.land = + LatexCmds.wedge = + bindBinaryOperator('\\wedge ', '∧', 'and'); + +LatexCmds.or = + LatexCmds.lor = + LatexCmds.vee = + bindBinaryOperator('\\vee ', '∨', 'or'); + +LatexCmds.o = + LatexCmds.O = + LatexCmds.empty = + LatexCmds.emptyset = + LatexCmds.oslash = + LatexCmds.Oslash = + LatexCmds.nothing = + LatexCmds.varnothing = + bindBinaryOperator('\\varnothing ', '∅', 'nothing'); + +LatexCmds.cup = LatexCmds.union = bindBinaryOperator( + '\\cup ', + '∪', + 'union' +); + +LatexCmds.cap = + LatexCmds.intersect = + LatexCmds.intersection = + bindBinaryOperator('\\cap ', '∩', 'intersection'); // FIXME: the correct LaTeX would be ^\circ but we can't parse that -LatexCmds.deg = LatexCmds.degree = bindVanillaSymbol('\\degree ','°', 'degrees'); - -LatexCmds.ang = LatexCmds.angle = bindVanillaSymbol('\\angle ','∠', 'angle'); -LatexCmds.measuredangle = bindVanillaSymbol('\\measuredangle ','∡', 'measured angle'); +LatexCmds.deg = LatexCmds.degree = bindVanillaSymbol( + '\\degree ', + '°', + 'degrees' +); + +LatexCmds.ang = LatexCmds.angle = bindVanillaSymbol( + '\\angle ', + '∠', + 'angle' +); +LatexCmds.measuredangle = bindVanillaSymbol( + '\\measuredangle ', + '∡', + 'measured angle' +); diff --git a/src/commands/math/basicSymbols.ts b/src/commands/math/basicSymbols.ts index 80b90cb53..dfc7945c9 100644 --- a/src/commands/math/basicSymbols.ts +++ b/src/commands/math/basicSymbols.ts @@ -2,22 +2,28 @@ * Symbols for Basic Mathematics ********************************/ class DigitGroupingChar extends MQSymbol { - finalizeTree (opts:CursorOptions, dir:Direction) { this.sharedSiblingMethod(opts, dir) }; - siblingDeleted (opts:CursorOptions, dir:Direction) { this.sharedSiblingMethod(opts, dir) }; - siblingCreated (opts:CursorOptions, dir:Direction) { this.sharedSiblingMethod(opts, dir) }; + finalizeTree(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } + siblingDeleted(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } + siblingCreated(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } - sharedSiblingMethod (opts:CursorOptions, dir:Direction) { + sharedSiblingMethod(opts: CursorOptions, dir: Direction) { // don't try to fix digit grouping if the sibling to my right changed (dir === R or // undefined) and it's now a DigitGroupingChar, it will try to fix grouping if (dir !== L && this[R] instanceof DigitGroupingChar) return; this.fixDigitGrouping(opts); - }; + } - fixDigitGrouping (opts:CursorOptions) { + fixDigitGrouping(opts: CursorOptions) { if (!opts.enableDigitGrouping) return; - var left:NodeRef = this; - var right:NodeRef = this; + var left: NodeRef = this; + var right: NodeRef = this; var spacesFound = 0; var dots = []; @@ -26,30 +32,30 @@ class DigitGroupingChar extends MQSymbol { var DOT = '.'; // traverse left as far as possible (starting at this char) - var node:NodeRef = left; + var node: NodeRef = left; do { if (/^[0-9]$/.test(node.ctrlSeq!)) { - left = node + left = node; } else if (node.ctrlSeq === SPACE) { - left = node + left = node; spacesFound += 1; } else if (node.ctrlSeq === DOT) { - left = node + left = node; dots.push(node); } else { break; } - } while (node = left[L]); + } while ((node = left[L])); // traverse right as far as possible (starting to right of this char) - while (node = right[R]) { + while ((node = right[R])) { if (/^[0-9]$/.test(node.ctrlSeq!)) { - right = node + right = node; } else if (node.ctrlSeq === SPACE) { - right = node + right = node; spacesFound += 1; } else if (node.ctrlSeq === DOT) { - right = node + right = node; dots.push(node); } else { break; @@ -85,19 +91,19 @@ class DigitGroupingChar extends MQSymbol { } else { this.addGroupingBetween(right, left); } - }; + } - removeGroupingBetween (left:NodeRef, right:NodeRef) { + removeGroupingBetween(left: NodeRef, right: NodeRef) { var node = left; do { if (node instanceof DigitGroupingChar) { node.setGroupingClass(undefined); } if (!node || node === right) break; - } while (node = node[R]); - }; + } while ((node = node[R])); + } - addGroupingBetween (start:NodeRef, end:NodeRef) { + addGroupingBetween(start: NodeRef, end: NodeRef) { var node = start; var count = 0; @@ -125,12 +131,12 @@ class DigitGroupingChar extends MQSymbol { cls = 'mq-group-leading-' + numDigitsInFirstGroup; } else if (count % 3 === 0) { if (count !== totalDigits) { - cls = 'mq-group-start' + cls = 'mq-group-start'; } } if (!cls) { - cls = 'mq-group-other' + cls = 'mq-group-other'; } } @@ -141,10 +147,10 @@ class DigitGroupingChar extends MQSymbol { if (node === end) break; node = node[L] as DigitGroupingChar; } - }; + } - _groupingClass?:string; - setGroupingClass (cls:string | undefined) { + _groupingClass?: string; + setGroupingClass(cls: string | undefined) { // nothing changed (either class is the same or it's still undefined) if (this._groupingClass === cls) return; @@ -157,85 +163,96 @@ class DigitGroupingChar extends MQSymbol { // cache the groupingClass this._groupingClass = cls; } -}; +} class Digit extends DigitGroupingChar { - constructor (ch:string, html?:string, mathspeak?:string) { - super(ch, ''+(html || ch)+'', undefined, mathspeak); - }; + constructor(ch: string, html?: string, mathspeak?: string) { + super( + ch, + '' + (html || ch) + '', + undefined, + mathspeak + ); + } - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { const cursorL = cursor[L]; const cursorLL = cursorL && cursorL[L]; - const cursorParentParentSub = ( - cursor.parent.parent instanceof SupSub ? - cursor.parent.parent.sub - : undefined - ) - - if (cursor.options.autoSubscriptNumerals - && cursor.parent !== cursorParentParentSub - && ((cursorL instanceof Variable && cursorL.isItalic !== false) - || (cursorL instanceof SupSub - && cursorLL instanceof Variable - && cursorLL.isItalic !== false))) { + const cursorParentParentSub = + cursor.parent.parent instanceof SupSub + ? cursor.parent.parent.sub + : undefined; + + if ( + cursor.options.autoSubscriptNumerals && + cursor.parent !== cursorParentParentSub && + ((cursorL instanceof Variable && cursorL.isItalic !== false) || + (cursorL instanceof SupSub && + cursorLL instanceof Variable && + cursorLL.isItalic !== false)) + ) { new SubscriptCommand().createLeftOf(cursor); super.createLeftOf(cursor); cursor.insRightOf(cursor.parent.parent); - } - else super.createLeftOf(cursor); - }; - mathspeak (opts:MathspeakOptions) { + } else super.createLeftOf(cursor); + } + mathspeak(opts: MathspeakOptions) { if (opts && opts.createdLeftOf) { var cursor = opts.createdLeftOf; var cursorL = cursor[L]; var cursorLL = cursorL && cursorL[L]; - const cursorParentParentSub = ( - cursor.parent.parent instanceof SupSub ? - cursor.parent.parent.sub - : undefined - ) - - if (cursor.options.autoSubscriptNumerals - && cursor.parent !== cursorParentParentSub - && ((cursorL instanceof Variable && cursorL.isItalic !== false) - || (cursor[L] instanceof SupSub - && cursorLL instanceof Variable - && cursorLL.isItalic !== false))) { + const cursorParentParentSub = + cursor.parent.parent instanceof SupSub + ? cursor.parent.parent.sub + : undefined; + + if ( + cursor.options.autoSubscriptNumerals && + cursor.parent !== cursorParentParentSub && + ((cursorL instanceof Variable && cursorL.isItalic !== false) || + (cursor[L] instanceof SupSub && + cursorLL instanceof Variable && + cursorLL.isItalic !== false)) + ) { return 'Subscript ' + super.mathspeak() + ' Baseline'; } } return super.mathspeak(); - }; + } } class Variable extends MQSymbol { - isItalic?:boolean; + isItalic?: boolean; - constructor (ch:string, html?:string) { - super(ch, ''+(html || ch)+''); - }; - text () { + constructor(ch: string, html?: string) { + super(ch, '' + (html || ch) + ''); + } + text() { var text = this.ctrlSeq || ''; if (this.isPartOfOperator) { if (text[0] == '\\') { text = text.slice(1, text.length); - } - else if (text[text.length-1] == ' ') { - text = text.slice (0, -1); + } else if (text[text.length - 1] == ' ') { + text = text.slice(0, -1); } } else { - if (this[L] && !(this[L] instanceof Variable) - && !(this[L] instanceof BinaryOperator) - && (this[L] as MQNode).ctrlSeq !== '\\ ') + if ( + this[L] && + !(this[L] instanceof Variable) && + !(this[L] instanceof BinaryOperator) && + (this[L] as MQNode).ctrlSeq !== '\\ ' + ) text = '*' + text; - if (this[R] && !(this[R] instanceof BinaryOperator) - && !(this[R] instanceof SupSub)) + if ( + this[R] && + !(this[R] instanceof BinaryOperator) && + !(this[R] instanceof SupSub) + ) text += '*'; } return text; - }; - mathspeak () { + } + mathspeak() { var text = this.ctrlSeq || ''; if ( this.isPartOfOperator || @@ -249,28 +266,27 @@ class Variable extends MQSymbol { // e.g. "y-2" is spoken as "ee minus 2" (as if the y is short). // Not an ideal solution, but surrounding non-numeric text blocks with quotation marks works. // This bug has been acknowledged by Apple. - return '"'+text+'"'; + return '"' + text + '"'; } - }; -}; -function bindVariable (ch:string, html:string, _unusedMathspeak?:string) { + } +} +function bindVariable(ch: string, html: string, _unusedMathspeak?: string) { return () => new Variable(ch, html); } - Options.prototype.autoCommands = { _maxLength: 0 }; -optionProcessors.autoCommands = function(cmds:string) { +optionProcessors.autoCommands = function (cmds: string) { if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) { - throw '"'+cmds+'" not a space-delimited list of only letters'; + throw '"' + cmds + '" not a space-delimited list of only letters'; } var list = cmds.split(' '); - var dict:AutoDict = {} + var dict: AutoDict = {}; var maxLength = 0; for (var i = 0; i < list.length; i += 1) { var cmd = list[i]; if (cmd.length < 2) { - throw 'autocommand "'+cmd+'" not minimum length of 2'; + throw 'autocommand "' + cmd + '" not minimum length of 2'; } if (LatexCmds[cmd] === OperatorName) { @@ -284,9 +300,9 @@ optionProcessors.autoCommands = function(cmds:string) { }; Options.prototype.quietEmptyDelimiters = {}; -optionProcessors.quietEmptyDelimiters = function(dlms:string) { +optionProcessors.quietEmptyDelimiters = function (dlms: string) { var list = dlms.split(' '); - var dict: { [id:string]:any; } = {}; + var dict: { [id: string]: any } = {}; for (var i = 0; i < list.length; i += 1) { var dlm = list[i]; dict[dlm] = 1; @@ -294,34 +310,34 @@ optionProcessors.quietEmptyDelimiters = function(dlms:string) { return dict; }; -Options.prototype.autoParenthesizedFunctions = {_maxLength: 0}; +Options.prototype.autoParenthesizedFunctions = { _maxLength: 0 }; optionProcessors.autoParenthesizedFunctions = function (cmds) { if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) { - throw '"'+cmds+'" not a space-delimited list of only letters'; + throw '"' + cmds + '" not a space-delimited list of only letters'; } var list = cmds.split(' '); - var dict:AutoDict = {} + var dict: AutoDict = {}; var maxLength = 0; for (var i = 0; i < list.length; i += 1) { var cmd = list[i]; if (cmd.length < 2) { - throw 'autocommand "'+cmd+'" not minimum length of 2'; + throw 'autocommand "' + cmd + '" not minimum length of 2'; } dict[cmd] = 1; maxLength = max(maxLength, cmd.length); } dict._maxLength = maxLength; return dict; -} +}; class Letter extends Variable { - letter:string; + letter: string; - constructor (ch:string) { + constructor(ch: string) { super(ch); this.letter = ch; - }; - checkAutoCmds (cursor:Cursor) { + } + checkAutoCmds(cursor: Cursor) { //exit early if in simple subscript and disableAutoSubstitutionInSubscripts is set. if (this.shouldIgnoreSubstitutionInSimpleSubscript(cursor.options)) { return; @@ -329,12 +345,12 @@ class Letter extends Variable { //handle autoCommands var autoCmds = cursor.options.autoCommands; - var maxLength = autoCmds._maxLength || 0; + var maxLength = autoCmds._maxLength || 0; if (maxLength > 0) { // want longest possible autocommand, so join together longest // sequence of letters var str = ''; - var l:NodeRef = this; + var l: NodeRef = this; var i = 0; // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) { @@ -366,11 +382,11 @@ class Letter extends Variable { } } - autoParenthesize (cursor:Cursor) { + autoParenthesize(cursor: Cursor) { //exit early if already parenthesized - var right = cursor.parent.ends[R] + var right = cursor.parent.ends[R]; if (right && right instanceof Bracket && right.ctrlSeq === '\\left(') { - return + return; } //exit early if in simple subscript and disableAutoSubstitutionInSubscripts is set. @@ -380,49 +396,58 @@ class Letter extends Variable { //handle autoParenthesized functions var str = ''; - var l:NodeRef = this + var l: NodeRef = this; var i = 0; var autoParenthesizedFunctions = cursor.options.autoParenthesizedFunctions; var maxLength = autoParenthesizedFunctions._maxLength || 0; - var autoOperatorNames = cursor.options.autoOperatorNames + var autoOperatorNames = cursor.options.autoOperatorNames; while (l instanceof Letter && i < maxLength) { - str = l.letter + str, l = l[L], i += 1; + (str = l.letter + str), (l = l[L]), (i += 1); } // check for an autoParenthesized functions, going thru substrings longest to shortest // only allow autoParenthesized functions that are also autoOperatorNames while (str.length) { - if (autoParenthesizedFunctions.hasOwnProperty(str) && autoOperatorNames.hasOwnProperty(str)) { + if ( + autoParenthesizedFunctions.hasOwnProperty(str) && + autoOperatorNames.hasOwnProperty(str) + ) { return cursor.parent.write(cursor, '('); } str = str.slice(1); } } - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { super.createLeftOf(cursor); this.checkAutoCmds(cursor); this.autoParenthesize(cursor); - }; - italicize (bool:boolean) { + } + italicize(bool: boolean) { this.isItalic = bool; this.isPartOfOperator = !bool; this.jQ.toggleClass('mq-operator-name', !bool); return this; - }; - finalizeTree (opts:CursorOptions, dir:Direction) {this.sharedSiblingMethod(opts, dir)}; - siblingDeleted (opts:CursorOptions, dir:Direction) {this.sharedSiblingMethod(opts, dir)}; - siblingCreated (opts:CursorOptions, dir:Direction) {this.sharedSiblingMethod(opts, dir)}; + } + finalizeTree(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } + siblingDeleted(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } + siblingCreated(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } - sharedSiblingMethod (opts:CursorOptions, dir:Direction) { + sharedSiblingMethod(opts: CursorOptions, dir: Direction) { // don't auto-un-italicize if the sibling to my right changed (dir === R or // undefined) and it's now a Letter, it will un-italicize everyone if (dir !== L && this[R] instanceof Letter) return; this.autoUnItalicize(opts); - }; + } - autoUnItalicize (opts:CursorOptions) { + autoUnItalicize(opts: CursorOptions) { var autoOps = opts.autoOperatorNames; if (autoOps._maxLength === 0) return; @@ -442,9 +467,14 @@ class Letter extends Variable { var lR = l && l[R]; var rL = r && r[L]; - new Fragment(lR || this.parent.ends[L] as MQNode, rL || this.parent.ends[R] as MQNode).each(function(el) { + new Fragment( + lR || (this.parent.ends[L] as MQNode), + rL || (this.parent.ends[R] as MQNode) + ).each(function (el) { if (el instanceof Letter) { - el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub'); + el.italicize(true).jQ.removeClass( + 'mq-first mq-last mq-followed-by-supsub' + ); el.ctrlSeq = el.letter; } return undefined; @@ -454,13 +484,21 @@ class Letter extends Variable { // check for operator names: at each position from left to right, check // substrings from longest to shortest - outer: for (var i = 0, first = (l as MQNode)[R] || this.parent.ends[L]; first && i < str.length; i += 1, first = (first as MQNode)[R]) { + outer: for ( + var i = 0, first = (l as MQNode)[R] || this.parent.ends[L]; + first && i < str.length; + i += 1, first = (first as MQNode)[R] + ) { for (var len = min(autoOpsLength, str.length - i); len > 0; len -= 1) { var word = str.slice(i, i + len); - var last:MQNode = undefined!; // TODO - TS complaining that we use last before assigning to it + var last: MQNode = undefined!; // TODO - TS complaining that we use last before assigning to it if (autoOps.hasOwnProperty(word)) { - for (var j = 0, letter:NodeRef = first; j < len; j += 1, letter = (letter as MQNode)[R]) { + for ( + var j = 0, letter: NodeRef = first; + j < len; + j += 1, letter = (letter as MQNode)[R] + ) { if (letter instanceof Letter) { letter.italicize(false); last = letter; @@ -468,9 +506,9 @@ class Letter extends Variable { } var isBuiltIn = BuiltInOpNames.hasOwnProperty(word); - first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq; - last.ctrlSeq += (isBuiltIn ? ' ' : '}'); - + first.ctrlSeq = + (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq; + last.ctrlSeq += isBuiltIn ? ' ' : '}'; if (TwoWordOpNames.hasOwnProperty(word)) { const lastL = last[L]; @@ -485,12 +523,17 @@ class Letter extends Variable { var supsub = last[R] as MQNode; // XXX monkey-patching, but what's the right thing here? // Have operatorname-specific code in SupSub? A CSS-like language to style the // math tree, but which ignores cursor and selection (which CSS can't)? - var respace = supsub.siblingCreated = supsub.siblingDeleted = function() { - supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket)); - }; + var respace = + (supsub.siblingCreated = + supsub.siblingDeleted = + function () { + supsub.jQ.toggleClass( + 'mq-after-operator-name', + !(supsub[R] instanceof Bracket) + ); + }); respace(); - } - else { + } else { last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket)); } } @@ -501,8 +544,8 @@ class Letter extends Variable { } } } - }; - shouldOmitPadding(node:NodeRef) { + } + shouldOmitPadding(node: NodeRef) { // omit padding if no node if (!node) return true; @@ -517,25 +560,32 @@ class Letter extends Variable { return false; } -}; -var BuiltInOpNames:AutoDict = {}; // the set of operator names like \sin, \cos, etc that - // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc - // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf' - // and 'arsinh', which must be exported as \operatorname{hcf} and - // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like - // \varlimsup are not supported -var AutoOpNames:AutoDict = Options.prototype.autoOperatorNames = { _maxLength: 9 }; // the set - // of operator names that MathQuill auto-unitalicizes by default; overridable +} +var BuiltInOpNames: AutoDict = {}; // the set of operator names like \sin, \cos, etc that +// are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc +// MathQuill auto-unitalicizes some operator names not in that set, like 'hcf' +// and 'arsinh', which must be exported as \operatorname{hcf} and +// \operatorname{arsinh}. Note: over/under line/arrow \lim variants like +// \varlimsup are not supported +var AutoOpNames: AutoDict = (Options.prototype.autoOperatorNames = { + _maxLength: 9, +}); // the set +// of operator names that MathQuill auto-unitalicizes by default; overridable var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 }; -(function() { - var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup' - + ' limsup liminf injlim projlim Pr').split(' '); +(function () { + var mostOps = ( + 'arg deg det dim exp gcd hom inf ker lg lim ln log max min sup' + + ' limsup liminf injlim projlim Pr' + ).split(' '); for (var i = 0; i < mostOps.length; i += 1) { BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1; } - var builtInTrigs = // why coth but not sech and csch, LaTeX? - 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' '); + var builtInTrigs = + 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split( + // why coth but not sech and csch, LaTeX? + ' ' + ); for (var i = 0; i < builtInTrigs.length; i += 1) { BuiltInOpNames[builtInTrigs[i]] = 1; } @@ -543,10 +593,11 @@ var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 }; var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' '); for (var i = 0; i < autoTrigs.length; i += 1) { AutoOpNames[autoTrigs[i]] = - AutoOpNames['arc'+autoTrigs[i]] = - AutoOpNames[autoTrigs[i]+'h'] = - AutoOpNames['ar'+autoTrigs[i]+'h'] = - AutoOpNames['arc'+autoTrigs[i]+'h'] = 1; + AutoOpNames['arc' + autoTrigs[i]] = + AutoOpNames[autoTrigs[i] + 'h'] = + AutoOpNames['ar' + autoTrigs[i] + 'h'] = + AutoOpNames['arc' + autoTrigs[i] + 'h'] = + 1; } // compat with some of the nonstandard LaTeX exported by MathQuill @@ -555,34 +606,35 @@ var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 }; for (var i = 0; i < moreNonstandardOps.length; i += 1) { AutoOpNames[moreNonstandardOps[i]] = 1; } -}()); +})(); -optionProcessors.autoOperatorNames = function(cmds) { - if(typeof cmds !== 'string') { - throw '"'+cmds+'" not a space-delimited list'; +optionProcessors.autoOperatorNames = function (cmds) { + if (typeof cmds !== 'string') { + throw '"' + cmds + '" not a space-delimited list'; } if (!/^[a-z\|\-]+(?: [a-z\|\-]+)*$/i.test(cmds)) { - throw '"'+cmds+'" not a space-delimited list of letters or "|"'; + throw '"' + cmds + '" not a space-delimited list of letters or "|"'; } var list = cmds.split(' '); - var dict:AutoDict = {}; + var dict: AutoDict = {}; var maxLength = 0; for (var i = 0; i < list.length; i += 1) { var cmd = list[i]; if (cmd.length < 2) { - throw '"'+cmd+'" not minimum length of 2'; + throw '"' + cmd + '" not minimum length of 2'; } - if(cmd.indexOf('|') < 0) { // normal auto operator + if (cmd.indexOf('|') < 0) { + // normal auto operator dict[cmd] = cmd; maxLength = max(maxLength, cmd.length); - } - else { // this item has a speech-friendly alternative + } else { + // this item has a speech-friendly alternative var cmdArray = cmd.split('|'); - if(cmdArray.length > 2) { - throw '"'+cmd+'" has more than 1 mathspeak delimiter'; + if (cmdArray.length > 2) { + throw '"' + cmd + '" has more than 1 mathspeak delimiter'; } if (cmdArray[0].length < 2) { - throw '"'+cmd[0]+'" not minimum length of 2'; + throw '"' + cmd[0] + '" not minimum length of 2'; } dict[cmdArray[0]] = cmdArray[1].replace(/-/g, ' '); // convert dashes to spaces for the sake of speech maxLength = max(maxLength, cmdArray[0].length); @@ -592,39 +644,42 @@ optionProcessors.autoOperatorNames = function(cmds) { return dict; }; class OperatorName extends MQSymbol { - ctrlSeq:string; - constructor (fn?:string) { + ctrlSeq: string; + constructor(fn?: string) { super(fn || ''); - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { var fn = this.ctrlSeq; for (var i = 0; i < fn.length; i += 1) { new Letter(fn.charAt(i)).createLeftOf(cursor); } - }; - parser () { + } + parser() { var fn = this.ctrlSeq; var block = new MathBlock(); for (var i = 0; i < fn.length; i += 1) { new Letter(fn.charAt(i)).adopt(block, block.ends[R], 0); } return Parser.succeed(block.children()) as ParserAny; - }; -}; -for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) { - (LatexCmds as LatexCmdsAny)[fn as string] = OperatorName; + } } +for (var fn in AutoOpNames) + if (AutoOpNames.hasOwnProperty(fn)) { + (LatexCmds as LatexCmdsAny)[fn as string] = OperatorName; + } LatexCmds.operatorname = class extends MathCommand { - createLeftOf () {}; - numBlocks () { return 1; }; - parser () { - return latexMathParser.block.map(function(b) { + createLeftOf() {} + numBlocks() { + return 1; + } + parser() { + return latexMathParser.block.map(function (b) { // Check for the special case of \operatorname{ans}, which has // a special html representation var isAllLetters = true; var str = ''; var children = b.children(); - children.each(function(child) { + children.each(function (child) { if (child instanceof Letter) { str += child.letter; } else { @@ -638,33 +693,39 @@ LatexCmds.operatorname = class extends MathCommand { // In cases other than `ans`, just return the children directly return children; }) as ParserAny; - }; + } }; LatexCmds.f = class extends Letter { - letter:string; + letter: string; constructor() { var letter = 'f'; super(letter); this.letter = letter; this.htmlTemplate = 'f'; - }; - italicize (bool:boolean) { + } + italicize(bool: boolean) { this.jQ.html('f').toggleClass('mq-f', bool); return super.italicize(bool); - }; + } }; // VanillaSymbol's -LatexCmds[' '] = LatexCmds.space = () => new DigitGroupingChar('\\ ', ' ', ' '); +LatexCmds[' '] = LatexCmds.space = () => + new DigitGroupingChar('\\ ', ' ', ' '); -LatexCmds['.'] = () => new DigitGroupingChar('.', '.', '.') +LatexCmds['.'] = () => + new DigitGroupingChar('.', '.', '.'); LatexCmds["'"] = LatexCmds.prime = bindVanillaSymbol("'", '′', 'prime'); -LatexCmds['″'] = LatexCmds.dprime = bindVanillaSymbol('″', '″', 'double prime'); +LatexCmds['″'] = LatexCmds.dprime = bindVanillaSymbol( + '″', + '″', + 'double prime' +); -LatexCmds.backslash = bindVanillaSymbol('\\backslash ','\\', 'backslash'); +LatexCmds.backslash = bindVanillaSymbol('\\backslash ', '\\', 'backslash'); if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash; LatexCmds.$ = bindVanillaSymbol('\\$', '$', 'dollar'); @@ -674,18 +735,18 @@ LatexCmds.mid = bindVanillaSymbol('\\mid ', '\u2223', 'mid'); // does not use Symbola font class NonSymbolaSymbol extends MQSymbol { - constructor (ch:string, html?:string, _unusedMathspeak?:string) { - super(ch, ''+(html || ch)+''); - }; -}; + constructor(ch: string, html?: string, _unusedMathspeak?: string) { + super(ch, '' + (html || ch) + ''); + } +} LatexCmds['@'] = () => new NonSymbolaSymbol('@'); LatexCmds['&'] = () => new NonSymbolaSymbol('\\&', '&', 'and'); LatexCmds['%'] = class extends NonSymbolaSymbol { constructor() { super('\\%', '%', 'percent'); - }; - parser () { + } + parser() { var optWhitespace = Parser.optWhitespace; var string = Parser.string; @@ -693,131 +754,151 @@ LatexCmds['%'] = class extends NonSymbolaSymbol { // it will be serialized properly and deleted as a unit. return optWhitespace .then( - string('\\operatorname{of}') - .map(function () { + string('\\operatorname{of}').map(function () { return PercentOfBuilder(); }) - ).or(super.parser()) as ParserAny - ; + ) + .or(super.parser()) as ParserAny; } }; -LatexCmds['∥'] = LatexCmds.parallel = - bindVanillaSymbol('\\parallel ', '∥', 'parallel'); +LatexCmds['∥'] = LatexCmds.parallel = bindVanillaSymbol( + '\\parallel ', + '∥', + 'parallel' +); -LatexCmds['∦'] = LatexCmds.nparallel = - bindVanillaSymbol('\\nparallel ', '∦', 'not parallel'); +LatexCmds['∦'] = LatexCmds.nparallel = bindVanillaSymbol( + '\\nparallel ', + '∦', + 'not parallel' +); -LatexCmds['⟂'] = LatexCmds.perp = - bindVanillaSymbol('\\perp ', '⟂', 'perpendicular'); +LatexCmds['⟂'] = LatexCmds.perp = bindVanillaSymbol( + '\\perp ', + '⟂', + 'perpendicular' +); //the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html //lowercase Greek letter variables LatexCmds.alpha = -LatexCmds.beta = -LatexCmds.gamma = -LatexCmds.delta = -LatexCmds.zeta = -LatexCmds.eta = -LatexCmds.theta = -LatexCmds.iota = -LatexCmds.kappa = -LatexCmds.mu = -LatexCmds.nu = -LatexCmds.xi = -LatexCmds.rho = -LatexCmds.sigma = -LatexCmds.tau = -LatexCmds.chi = -LatexCmds.psi = -LatexCmds.omega = (latex) => new Variable('\\'+latex+' ','&'+latex+';'); + LatexCmds.beta = + LatexCmds.gamma = + LatexCmds.delta = + LatexCmds.zeta = + LatexCmds.eta = + LatexCmds.theta = + LatexCmds.iota = + LatexCmds.kappa = + LatexCmds.mu = + LatexCmds.nu = + LatexCmds.xi = + LatexCmds.rho = + LatexCmds.sigma = + LatexCmds.tau = + LatexCmds.chi = + LatexCmds.psi = + LatexCmds.omega = + (latex) => new Variable('\\' + latex + ' ', '&' + latex + ';'); //why can't anybody FUCKING agree on these -LatexCmds.phi = //W3C or Unicode? - bindVariable('\\phi ','ϕ', 'phi'); +LatexCmds.phi = bindVariable('\\phi ', 'ϕ', 'phi'); //W3C or Unicode? -LatexCmds.phiv = //Elsevier and 9573-13 -LatexCmds.varphi = //AMS and LaTeX - bindVariable('\\varphi ','φ', 'phi'); +LatexCmds.phiv = LatexCmds.varphi = bindVariable('\\varphi ', 'φ', 'phi'); //Elsevier and 9573-13 //AMS and LaTeX -LatexCmds.epsilon = //W3C or Unicode? - bindVariable('\\epsilon ','ϵ', 'epsilon'); +LatexCmds.epsilon = bindVariable('\\epsilon ', 'ϵ', 'epsilon'); //W3C or Unicode? -LatexCmds.epsiv = //Elsevier and 9573-13 -LatexCmds.varepsilon = //AMS and LaTeX - bindVariable('\\varepsilon ','ε', 'epsilon'); +LatexCmds.epsiv = LatexCmds.varepsilon = bindVariable( + //Elsevier and 9573-13 //AMS and LaTeX + '\\varepsilon ', + 'ε', + 'epsilon' +); -LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13 -LatexCmds.varpi = //AMS and LaTeX - bindVariable('\\varpi ','ϖ', 'piv'); +LatexCmds.piv = LatexCmds.varpi = bindVariable('\\varpi ', 'ϖ', 'piv'); //W3C/Unicode and Elsevier and 9573-13 //AMS and LaTeX LatexCmds.sigmaf = //W3C/Unicode -LatexCmds.sigmav = //Elsevier -LatexCmds.varsigma = //LaTeX - bindVariable('\\varsigma ','ς', 'sigma'); + LatexCmds.sigmav = //Elsevier + LatexCmds.varsigma = //LaTeX + bindVariable('\\varsigma ', 'ς', 'sigma'); LatexCmds.thetav = //Elsevier and 9573-13 -LatexCmds.vartheta = //AMS and LaTeX -LatexCmds.thetasym = //W3C/Unicode - bindVariable('\\vartheta ','ϑ', 'theta'); + LatexCmds.vartheta = //AMS and LaTeX + LatexCmds.thetasym = //W3C/Unicode + bindVariable('\\vartheta ', 'ϑ', 'theta'); -LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode -LatexCmds.upsi = //Elsevier and 9573-13 - bindVariable('\\upsilon ','υ', 'upsilon'); +LatexCmds.upsilon = LatexCmds.upsi = bindVariable( + //AMS and LaTeX and W3C/Unicode //Elsevier and 9573-13 + '\\upsilon ', + 'υ', + 'upsilon' +); //these aren't even mentioned in the HTML character entity references LatexCmds.gammad = //Elsevier -LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above) -LatexCmds.digamma = //LaTeX - bindVariable('\\digamma ','ϝ', 'gamma'); + LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above) + LatexCmds.digamma = //LaTeX + bindVariable('\\digamma ', 'ϝ', 'gamma'); -LatexCmds.kappav = //Elsevier -LatexCmds.varkappa = //AMS and LaTeX - bindVariable('\\varkappa ','ϰ', 'kappa'); +LatexCmds.kappav = LatexCmds.varkappa = bindVariable( + //Elsevier //AMS and LaTeX + '\\varkappa ', + 'ϰ', + 'kappa' +); -LatexCmds.rhov = //Elsevier and 9573-13 -LatexCmds.varrho = //AMS and LaTeX - bindVariable('\\varrho ','ϱ', 'rho'); +LatexCmds.rhov = LatexCmds.varrho = bindVariable('\\varrho ', 'ϱ', 'rho'); //Elsevier and 9573-13 //AMS and LaTeX //Greek constants, look best in non-italicized Times New Roman -LatexCmds.pi = LatexCmds['π'] = () => new NonSymbolaSymbol('\\pi ','π', 'pi'); -LatexCmds.lambda = () => new NonSymbolaSymbol('\\lambda ','λ', 'lambda'); +LatexCmds.pi = LatexCmds['π'] = () => + new NonSymbolaSymbol('\\pi ', 'π', 'pi'); +LatexCmds.lambda = () => + new NonSymbolaSymbol('\\lambda ', 'λ', 'lambda'); //uppercase greek letters LatexCmds.Upsilon = //LaTeX -LatexCmds.Upsi = //Elsevier and 9573-13 -LatexCmds.upsih = //W3C/Unicode "upsilon with hook" -LatexCmds.Upsih = //'cos it makes sense to me - () => new MQSymbol('\\Upsilon ','ϒ', 'capital upsilon'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :( + LatexCmds.Upsi = //Elsevier and 9573-13 + LatexCmds.upsih = //W3C/Unicode "upsilon with hook" + LatexCmds.Upsih = //'cos it makes sense to me + () => + new MQSymbol( + '\\Upsilon ', + 'ϒ', + 'capital upsilon' + ); //Symbola's 'upsilon with a hook' is a capital Y without hooks :( //other symbols with the same LaTeX command and HTML character entity reference LatexCmds.Gamma = -LatexCmds.Delta = -LatexCmds.Theta = -LatexCmds.Lambda = -LatexCmds.Xi = -LatexCmds.Pi = -LatexCmds.Sigma = -LatexCmds.Phi = -LatexCmds.Psi = -LatexCmds.Omega = -LatexCmds.forall = (latex) => new VanillaSymbol('\\'+latex+' ','&'+latex+';') + LatexCmds.Delta = + LatexCmds.Theta = + LatexCmds.Lambda = + LatexCmds.Xi = + LatexCmds.Pi = + LatexCmds.Sigma = + LatexCmds.Phi = + LatexCmds.Psi = + LatexCmds.Omega = + LatexCmds.forall = + (latex) => new VanillaSymbol('\\' + latex + ' ', '&' + latex + ';'); // symbols that aren't a single MathCommand, but are instead a whole // Fragment. Creates the Fragment from a LaTeX string class LatexFragment extends MathCommand { - latexStr:string; + latexStr: string; - constructor (latex:string) { + constructor(latex: string) { super(); this.latexStr = latex; } - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { var block = latexMathParser.parse(this.latexStr); - block.children().adopt(cursor.parent, cursor[L] as MQNode, cursor[R] as MQNode); + block + .children() + .adopt(cursor.parent, cursor[L] as MQNode, cursor[R] as MQNode); cursor[L] = block.ends[R]; block.jQize().insertBefore(cursor.jQ); block.finalizeInsert(cursor.options, cursor); @@ -825,16 +906,21 @@ class LatexFragment extends MathCommand { var blockEndsRR = blockEndsR && blockEndsR[R]; if (blockEndsRR) blockEndsRR.siblingCreated(cursor.options, L); var blockEndsL = block.ends[L]; - var blockEndsLL = blockEndsL && blockEndsL[L] ; + var blockEndsLL = blockEndsL && blockEndsL[L]; if (blockEndsLL) blockEndsLL.siblingCreated(cursor.options, R); - cursor.parent.bubble(function (node) { node.reflow(); return undefined; }); - }; - mathspeak () { return latexMathParser.parse(this.latexStr).mathspeak(); }; - parser () { + cursor.parent.bubble(function (node) { + node.reflow(); + return undefined; + }); + } + mathspeak() { + return latexMathParser.parse(this.latexStr).mathspeak(); + } + parser() { var frag = latexMathParser.parse(this.latexStr).children(); return Parser.succeed(frag) as ParserAny; - }; -}; + } +} // for what seems to me like [stupid reasons][1], Unicode provides // subscripted and superscripted versions of all ten Arabic numerals, @@ -901,7 +987,7 @@ LatexCmds['√'] = () => new LatexFragment('\\sqrt{}'); // Binary operator determination is used in several contexts for PlusMinus nodes and their descendants. // For instance, we set the item's class name based on this factor, and also assign different mathspeak values (plus vs positive, negative vs minus). -function isBinaryOperator(node:NodeRef):boolean { +function isBinaryOperator(node: NodeRef): boolean { if (!node) return false; const nodeL = node[L]; @@ -910,10 +996,17 @@ function isBinaryOperator(node:NodeRef):boolean { // If the left sibling is a binary operator or a separator (comma, semicolon, colon, space) // or an open bracket (open parenthesis, open square bracket) // consider the operator to be unary - if (nodeL instanceof BinaryOperator || /^(\\ )|[,;:\(\[]$/.test(nodeL.ctrlSeq!)) { + if ( + nodeL instanceof BinaryOperator || + /^(\\ )|[,;:\(\[]$/.test(nodeL.ctrlSeq!) + ) { return false; } - } else if (node.parent && node.parent.parent && node.parent.parent.isStyleBlock()) { + } else if ( + node.parent && + node.parent.parent && + node.parent.parent.isStyleBlock() + ) { //if we are in a style block at the leftmost edge, determine unary/binary based on //the style block //this allows style blocks to be transparent for unary/binary purposes @@ -926,188 +1019,255 @@ function isBinaryOperator(node:NodeRef):boolean { } var PlusMinus = class extends BinaryOperator { - constructor (ch?:string, html?:string, mathspeak?:string) { + constructor(ch?: string, html?: string, mathspeak?: string) { super(ch, html, undefined, mathspeak, true); - }; + } - contactWeld (cursor:Cursor, dir?:Direction) { this.sharedSiblingMethod(cursor.options, dir)} - siblingCreated (opts:CursorOptions, dir:Direction) { this.sharedSiblingMethod(opts, dir)} - siblingDeleted (opts:CursorOptions, dir:Direction) { this.sharedSiblingMethod(opts, dir)} + contactWeld(cursor: Cursor, dir?: Direction) { + this.sharedSiblingMethod(cursor.options, dir); + } + siblingCreated(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } + siblingDeleted(opts: CursorOptions, dir: Direction) { + this.sharedSiblingMethod(opts, dir); + } - sharedSiblingMethod (_opts?:CursorOptions, dir?:Direction) { + sharedSiblingMethod(_opts?: CursorOptions, dir?: Direction) { if (dir === R) return; // ignore if sibling only changed on the right - this.jQ[0].className = isBinaryOperator(this) - ? 'mq-binary-operator' - : ''; + this.jQ[0].className = isBinaryOperator(this) ? 'mq-binary-operator' : ''; return this; - }; + } }; LatexCmds['+'] = class extends PlusMinus { - constructor () { + constructor() { super('+', '+'); - }; - mathspeak ():string { + } + mathspeak(): string { return isBinaryOperator(this) ? 'plus' : 'positive'; - }; + } }; //yes, these are different dashes, en-dash, em-dash, unicode minus, actual dash class MinusNode extends PlusMinus { - constructor () { + constructor() { super('-', '−'); - }; - mathspeak ():string { + } + mathspeak(): string { return isBinaryOperator(this) ? 'minus' : 'negative'; - }; -}; + } +} LatexCmds['−'] = LatexCmds['—'] = LatexCmds['–'] = LatexCmds['-'] = MinusNode; -LatexCmds['±'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus = - () => new PlusMinus('\\pm ','±', 'plus-or-minus'); -LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus = - () => new PlusMinus('\\mp ','∓', 'minus-or-plus'); - -CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot = - bindBinaryOperator('\\cdot ', '·', '*', 'times'); //semantically should be ⋅, but · looks better +LatexCmds['±'] = + LatexCmds.pm = + LatexCmds.plusmn = + LatexCmds.plusminus = + () => new PlusMinus('\\pm ', '±', 'plus-or-minus'); +LatexCmds.mp = + LatexCmds.mnplus = + LatexCmds.minusplus = + () => new PlusMinus('\\mp ', '∓', 'minus-or-plus'); + +CharCmds['*'] = + LatexCmds.sdot = + LatexCmds.cdot = + bindBinaryOperator('\\cdot ', '·', '*', 'times'); //semantically should be ⋅, but · looks better class To extends BinaryOperator { - constructor () { - super('\\to ','→', 'to'); + constructor() { + super('\\to ', '→', 'to'); } - deleteTowards (dir:Direction, cursor:Cursor) { + deleteTowards(dir: Direction, cursor: Cursor) { if (dir === L) { var l = cursor[L] as MQNode; new Fragment(l, this).remove(); cursor[L] = l[L]; new MinusNode().createLeftOf(cursor); - (cursor[L] as MQNode).bubble(function (node) { node.reflow(); return undefined }); + (cursor[L] as MQNode).bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.deleteTowards(dir, cursor); - }; + } } LatexCmds['→'] = LatexCmds.to = To; class Inequality extends BinaryOperator { - strict:boolean; - data:InequalityData; - - constructor (data:InequalityData, strict:boolean) { - var strictness:''|'Strict' = (strict ? 'Strict' : ''); - super(data[`ctrlSeq${strictness}`], data[`html${strictness}`], - data[`text${strictness}`], data[`mathspeak${strictness}`]); + strict: boolean; + data: InequalityData; + + constructor(data: InequalityData, strict: boolean) { + var strictness: '' | 'Strict' = strict ? 'Strict' : ''; + super( + data[`ctrlSeq${strictness}`], + data[`html${strictness}`], + data[`text${strictness}`], + data[`mathspeak${strictness}`] + ); this.data = data; this.strict = strict; } - swap (strict:boolean) { + swap(strict: boolean) { this.strict = strict; - var strictness:''|'Strict' = (strict ? 'Strict' : ''); + var strictness: '' | 'Strict' = strict ? 'Strict' : ''; this.ctrlSeq = this.data[`ctrlSeq${strictness}`]; this.jQ.html(this.data[`html${strictness}`]); - this.textTemplate = [this.data[`text${strictness}`] ]; + this.textTemplate = [this.data[`text${strictness}`]]; this.mathspeakName = this.data[`mathspeak${strictness}`]; - }; - deleteTowards (dir:Direction, cursor:Cursor) { + } + deleteTowards(dir: Direction, cursor: Cursor) { if (dir === L && !this.strict) { this.swap(true); - this.bubble(function (node) { node.reflow(); return undefined }); + this.bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.deleteTowards(dir, cursor); - }; -}; + } +} -var less:InequalityData = { ctrlSeq: '\\le ', html: '≤', text: '≤', mathspeak: 'less than or equal to', - ctrlSeqStrict: '<', htmlStrict: '<', textStrict: '<', mathspeakStrict: 'less than'}; -var greater:InequalityData = { ctrlSeq: '\\ge ', html: '≥', text: '≥', mathspeak: 'greater than or equal to', - ctrlSeqStrict: '>', htmlStrict: '>', textStrict: '>', mathspeakStrict: 'greater than'}; +var less: InequalityData = { + ctrlSeq: '\\le ', + html: '≤', + text: '≤', + mathspeak: 'less than or equal to', + ctrlSeqStrict: '<', + htmlStrict: '<', + textStrict: '<', + mathspeakStrict: 'less than', +}; +var greater: InequalityData = { + ctrlSeq: '\\ge ', + html: '≥', + text: '≥', + mathspeak: 'greater than or equal to', + ctrlSeqStrict: '>', + htmlStrict: '>', + textStrict: '>', + mathspeakStrict: 'greater than', +}; class Greater extends Inequality { - constructor () { + constructor() { super(greater, true); - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { const cursorL = cursor[L]; if (cursorL instanceof BinaryOperator && cursorL.ctrlSeq === '-') { var l = cursorL; cursor[L] = l[L]; l.remove(); new To().createLeftOf(cursor); - (cursor[L] as MQNode).bubble(function (node) { node.reflow(); return undefined }); + (cursor[L] as MQNode).bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.createLeftOf(cursor); - }; + } } LatexCmds['<'] = LatexCmds.lt = () => new Inequality(less, true); LatexCmds['>'] = LatexCmds.gt = Greater; -LatexCmds['≤'] = LatexCmds.le = LatexCmds.leq = () => new Inequality(less, false); -LatexCmds['≥'] = LatexCmds.ge = LatexCmds.geq = () => new Inequality(greater, false); -LatexCmds.infty = LatexCmds.infin = LatexCmds.infinity = - bindVanillaSymbol('\\infty ','∞', 'infinity'); -LatexCmds['≠'] = LatexCmds.ne = LatexCmds.neq = bindBinaryOperator('\\ne ','≠', 'not equal'); +LatexCmds['≤'] = + LatexCmds.le = + LatexCmds.leq = + () => new Inequality(less, false); +LatexCmds['≥'] = + LatexCmds.ge = + LatexCmds.geq = + () => new Inequality(greater, false); +LatexCmds.infty = + LatexCmds.infin = + LatexCmds.infinity = + bindVanillaSymbol('\\infty ', '∞', 'infinity'); +LatexCmds['≠'] = + LatexCmds.ne = + LatexCmds.neq = + bindBinaryOperator('\\ne ', '≠', 'not equal'); class Equality extends BinaryOperator { - constructor () { + constructor() { super('=', '=', '=', 'equals'); - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { var cursorL = cursor[L]; if (cursorL instanceof Inequality && cursorL.strict) { cursorL.swap(false); - cursorL.bubble(function (node) { node.reflow(); return undefined}); + cursorL.bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.createLeftOf(cursor); - }; -}; + } +} LatexCmds['='] = Equality; -LatexCmds['×'] = LatexCmds.times = bindBinaryOperator('\\times ', '×', '[x]', 'times'); - -LatexCmds['÷'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides = - bindBinaryOperator('\\div ','÷', '[/]', 'over'); +LatexCmds['×'] = LatexCmds.times = bindBinaryOperator( + '\\times ', + '×', + '[x]', + 'times' +); +LatexCmds['÷'] = + LatexCmds.div = + LatexCmds.divide = + LatexCmds.divides = + bindBinaryOperator('\\div ', '÷', '[/]', 'over'); class Sim extends BinaryOperator { - constructor () { + constructor() { super('\\sim ', '~', '~', 'tilde'); - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { if (cursor[L] instanceof Sim) { var l = cursor[L] as MQNode; cursor[L] = l[L]; l.remove(); new Approx().createLeftOf(cursor); - (cursor[L] as MQNode).bubble(function (node) { node.reflow(); return undefined }); + (cursor[L] as MQNode).bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.createLeftOf(cursor); - }; -}; + } +} class Approx extends BinaryOperator { - constructor () { + constructor() { super('\\approx ', '≈', '≈', 'approximately equal'); - }; - deleteTowards (dir:Direction, cursor:Cursor) { + } + deleteTowards(dir: Direction, cursor: Cursor) { if (dir === L) { var l = cursor[L] as MQNode; new Fragment(l, this).remove(); cursor[L] = l[L]; new Sim().createLeftOf(cursor); - (cursor[L] as MQNode).bubble(function (node) { node.reflow(); return undefined }); + (cursor[L] as MQNode).bubble(function (node) { + node.reflow(); + return undefined; + }); return; } super.deleteTowards(dir, cursor); - }; -}; + } +} CharCmds['~'] = LatexCmds.sim = Sim; LatexCmds['≈'] = LatexCmds.approx = Approx; diff --git a/src/commands/math/commands.ts b/src/commands/math/commands.ts index 654045a23..77d62f934 100644 --- a/src/commands/math/commands.ts +++ b/src/commands/math/commands.ts @@ -2,138 +2,182 @@ * Commands and Operators. **************************/ var SVG_SYMBOLS = { - 'sqrt': { + sqrt: { width: '', html: '' + - '' + - '' + '' + + '', }, '|': { width: '.4em', html: '' + - '' + - '' + '' + + '', }, '[': { width: '.55em', html: '' + - '' + - '' + '' + + '', }, ']': { width: '.55em', html: '' + - '' + - '' + '' + + '', }, '(': { width: '.55em', html: '' + - '' + - '' + '' + + '', }, ')': { width: '.55em', html: '' + - '' + - '' + '' + + '', }, '{': { width: '.7em', html: '' + - '' + - '' + '' + + '', }, '}': { width: '.7em', html: '' + - '' + - '' + '' + + '', }, '∥': { width: '.7em', html: '' + - '' + - '' + '' + + '', }, '⟨': { width: '.55em', html: '' + - '' + - '' + '' + + '', }, '⟩': { width: '.55em', html: '' + - '' + - '' - } + '' + + '', + }, }; class Style extends MathCommand { - shouldNotSpeakDelimiters:boolean | undefined; - - constructor (ctrlSeq:string, tagName:string, attrs:string, ariaLabel?:string, opts?:{shouldNotSpeakDelimiters: boolean}) { - super(ctrlSeq, '<'+tagName+' '+attrs+'>&0'); + shouldNotSpeakDelimiters: boolean | undefined; + + constructor( + ctrlSeq: string, + tagName: string, + attrs: string, + ariaLabel?: string, + opts?: { shouldNotSpeakDelimiters: boolean } + ) { + super(ctrlSeq, '<' + tagName + ' ' + attrs + '>&0'); this.ariaLabel = ariaLabel || ctrlSeq.replace(/^\\/, ''); - this.mathspeakTemplate = ['Start' + this.ariaLabel + ',', 'End' + this.ariaLabel]; + this.mathspeakTemplate = [ + 'Start' + this.ariaLabel + ',', + 'End' + this.ariaLabel, + ]; // In most cases, mathspeak should announce the start and end of style blocks. // There is one exception currently (mathrm). this.shouldNotSpeakDelimiters = opts && opts.shouldNotSpeakDelimiters; - }; - mathspeak (opts?:MathspeakOptions) { - if ( - !this.shouldNotSpeakDelimiters || - (opts && opts.ignoreShorthand) - ) { + } + mathspeak(opts?: MathspeakOptions) { + if (!this.shouldNotSpeakDelimiters || (opts && opts.ignoreShorthand)) { return super.mathspeak(); } - return this.foldChildren('', function(speech, block) { + return this.foldChildren('', function (speech, block) { return speech + ' ' + block.mathspeak(opts); }).trim(); - }; -}; + } +} //fonts LatexCmds.mathrm = class extends Style { - constructor () { - super('\\mathrm', 'span', 'class="mq-roman mq-font"', 'Roman Font', { shouldNotSpeakDelimiters: true }); - }; - isTextBlock () { + constructor() { + super('\\mathrm', 'span', 'class="mq-roman mq-font"', 'Roman Font', { + shouldNotSpeakDelimiters: true, + }); + } + isTextBlock() { return true; - }; + } }; -LatexCmds.mathit = () => new Style('\\mathit', 'i', 'class="mq-font"', 'Italic Font'); -LatexCmds.mathbf = () => new Style('\\mathbf', 'b', 'class="mq-font"', 'Bold Font'); -LatexCmds.mathsf = () => new Style('\\mathsf', 'span', 'class="mq-sans-serif mq-font"', 'Serif Font'); -LatexCmds.mathtt = () => new Style('\\mathtt', 'span', 'class="mq-monospace mq-font"', 'Math Text'); +LatexCmds.mathit = () => + new Style('\\mathit', 'i', 'class="mq-font"', 'Italic Font'); +LatexCmds.mathbf = () => + new Style('\\mathbf', 'b', 'class="mq-font"', 'Bold Font'); +LatexCmds.mathsf = () => + new Style('\\mathsf', 'span', 'class="mq-sans-serif mq-font"', 'Serif Font'); +LatexCmds.mathtt = () => + new Style('\\mathtt', 'span', 'class="mq-monospace mq-font"', 'Math Text'); //text-decoration -LatexCmds.underline = () => new Style('\\underline', 'span', 'class="mq-non-leaf mq-underline"', 'Underline'); -LatexCmds.overline = LatexCmds.bar = () => new Style('\\overline', 'span', 'class="mq-non-leaf mq-overline"', 'Overline'); -LatexCmds.overrightarrow = () => new Style('\\overrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-right"', 'Over Right Arrow'); -LatexCmds.overleftarrow = () => new Style('\\overleftarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-left"', 'Over Left Arrow'); -LatexCmds.overleftrightarrow = () => new Style('\\overleftrightarrow ', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-leftright"', 'Over Left and Right Arrow'); -LatexCmds.overarc = () => new Style('\\overarc', 'span', 'class="mq-non-leaf mq-overarc"', 'Over Arc'); +LatexCmds.underline = () => + new Style( + '\\underline', + 'span', + 'class="mq-non-leaf mq-underline"', + 'Underline' + ); +LatexCmds.overline = LatexCmds.bar = () => + new Style( + '\\overline', + 'span', + 'class="mq-non-leaf mq-overline"', + 'Overline' + ); +LatexCmds.overrightarrow = () => + new Style( + '\\overrightarrow', + 'span', + 'class="mq-non-leaf mq-overarrow mq-arrow-right"', + 'Over Right Arrow' + ); +LatexCmds.overleftarrow = () => + new Style( + '\\overleftarrow', + 'span', + 'class="mq-non-leaf mq-overarrow mq-arrow-left"', + 'Over Left Arrow' + ); +LatexCmds.overleftrightarrow = () => + new Style( + '\\overleftrightarrow ', + 'span', + 'class="mq-non-leaf mq-overarrow mq-arrow-leftright"', + 'Over Left and Right Arrow' + ); +LatexCmds.overarc = () => + new Style('\\overarc', 'span', 'class="mq-non-leaf mq-overarc"', 'Over Arc'); LatexCmds.dot = () => { - return new MathCommand('\\dot', '' - + '˙' - + '&0' - + '' - ); + return new MathCommand( + '\\dot', + '' + + '˙' + + '&0' + + '' + ); }; - // `\textcolor{color}{math}` will apply a color to the given math content, where // `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended), // [Mozilla docs][], or [W3C spec][]). @@ -142,20 +186,23 @@ LatexCmds.dot = () => { // [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values // [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits LatexCmds.textcolor = class extends MathCommand { - color:string | undefined; - - setColor (color:string) { + color: string | undefined; + + setColor(color: string) { this.color = color; this.htmlTemplate = '&0'; this.ariaLabel = color.replace(/^\\/, ''); - this.mathspeakTemplate = ['Start ' + this.ariaLabel + ',', 'End ' + this.ariaLabel]; - }; - latex () { + this.mathspeakTemplate = [ + 'Start ' + this.ariaLabel + ',', + 'End ' + this.ariaLabel, + ]; + } + latex() { var blocks0 = this.blocks![0]; return '\\textcolor{' + this.color + '}{' + blocks0.latex() + '}'; - }; - parser () { + } + parser() { var optWhitespace = Parser.optWhitespace; var string = Parser.string; var regex = Parser.regex; @@ -167,44 +214,46 @@ LatexCmds.textcolor = class extends MathCommand { .then((color) => { this.setColor(color); return super.parser(); - }) - ; - }; - isStyleBlock () { + }); + } + isStyleBlock() { return true; - }; + } }; // Very similar to the \textcolor command, but will add the given CSS class. // Usage: \class{classname}{math} // Note regex that whitelists valid CSS classname characters: // https://github.com/mathquill/mathquill/pull/191#discussion_r4327442 -var Class = LatexCmds['class'] = class extends MathCommand { - cls:string | undefined; +var Class = (LatexCmds['class'] = class extends MathCommand { + cls: string | undefined; - parser () { - var string = Parser.string, regex = Parser.regex; + parser() { + var string = Parser.string, + regex = Parser.regex; return Parser.optWhitespace .then(string('{')) .then(regex(/^[-\w\s\\\xA0-\xFF]*/)) .skip(string('}')) .then((cls) => { this.cls = cls || ''; - this.htmlTemplate = '&0'; + this.htmlTemplate = '&0'; this.ariaLabel = cls + ' class'; - this.mathspeakTemplate = ['Start ' + this.ariaLabel + ',', 'End ' + this.ariaLabel]; + this.mathspeakTemplate = [ + 'Start ' + this.ariaLabel + ',', + 'End ' + this.ariaLabel, + ]; return super.parser(); - }) - ; - }; - latex () { + }); + } + latex() { var blocks0 = this.blocks![0]; return '\\class{' + this.cls + '}{' + blocks0.latex() + '}'; - }; - isStyleBlock () { + } + isStyleBlock() { return true; - }; -}; + } +}); // This test is used to determine whether an item may be treated as a whole number // for shortening the verbalized (mathspeak) forms of some fractions and superscripts. @@ -213,14 +262,18 @@ var intRgx = /^[\+\-]?[\d]+$/; // Traverses the top level of the passed block's children and returns the concatenation of their ctrlSeq properties. // Used in shortened mathspeak computations as a block's .text() method can be potentially expensive. // -function getCtrlSeqsFromBlock(block:NodeRef):string { +function getCtrlSeqsFromBlock(block: NodeRef): string { if (!block) return ''; var children = block.children(); if (!children || !children.ends[L]) return ''; - + var chars = ''; - for (var sibling:NodeRef | undefined = children.ends[L]; sibling && sibling[R] !== undefined; sibling = sibling[R]) { + for ( + var sibling: NodeRef | undefined = children.ends[L]; + sibling && sibling[R] !== undefined; + sibling = sibling[R] + ) { if (sibling.ctrlSeq !== undefined) chars += sibling.ctrlSeq; } return chars; @@ -230,15 +283,20 @@ Options.prototype.charsThatBreakOutOfSupSub = ''; class SupSub extends MathCommand { ctrlSeq = '_{...}^{...}'; - sub?:MathBlock; - sup?:MathBlock; + sub?: MathBlock; + sup?: MathBlock; supsub: 'sup' | 'sub'; - createLeftOf (cursor:Cursor) { - if (!this.replacedFragment && !cursor[L] && cursor.options.supSubsRequireOperand) return; + createLeftOf(cursor: Cursor) { + if ( + !this.replacedFragment && + !cursor[L] && + cursor.options.supSubsRequireOperand + ) + return; return super.createLeftOf(cursor); - }; - contactWeld (cursor:Cursor) { + } + contactWeld(cursor: Cursor) { // Look on either side for a SupSub, if one is found compare my // .sub, .sup with its .sub, .sup. If I have one that it doesn't, // then call .addBlock() on it with my block; if I have one that @@ -249,75 +307,91 @@ class SupSub extends MathCommand { // TODO: simplify // equiv. to [L, R].forEach(function(dir) { ... }); - for (var dir:L|R|false = L; dir; dir = (dir === L ? R : false)) { + for (var dir: L | R | false = L; dir; dir = dir === L ? R : false) { const thisDir = this[dir]; let pt; if (thisDir instanceof SupSub) { // equiv. to 'sub sup'.split(' ').forEach(function(supsub) { ... }); - for (var supsub:'sub'|'sup'|false = 'sub'; supsub; supsub = (supsub === 'sub' ? 'sup' : false)) { - var src = this[supsub], dest = thisDir[supsub]; + for ( + var supsub: 'sub' | 'sup' | false = 'sub'; + supsub; + supsub = supsub === 'sub' ? 'sup' : false + ) { + var src = this[supsub], + dest = thisDir[supsub]; if (!src) continue; if (!dest) thisDir.addBlock(src.disown()); - else if (!src.isEmpty()) { // ins src children at -dir end of dest + else if (!src.isEmpty()) { + // ins src children at -dir end of dest src.jQ.children().insAtDirEnd(-dir as Direction, dest.jQ); var children = src.children().disown(); pt = new Point(dest, children.ends[R], dest.ends[L]); if (dir === L) children.adopt(dest, dest.ends[R], 0); else children.adopt(dest, 0, dest.ends[L]); - } - else { + } else { pt = new Point(dest, 0, dest.ends[L]); } - this.placeCursor = (function(dest, src) { // TODO: don't monkey-patch - return function(cursor:Cursor) { cursor.insAtDirEnd(-dir as Direction, dest || src); }; - }(dest, src)); + this.placeCursor = (function (dest, src) { + // TODO: don't monkey-patch + return function (cursor: Cursor) { + cursor.insAtDirEnd(-dir as Direction, dest || src); + }; + })(dest, src); } this.remove(); if (cursor && cursor[L] === this) { if (dir === R && pt) { if (pt[L]) { - cursor.insRightOf(pt[L] as MQNode) - } else{ - cursor.insAtLeftEnd(pt.parent); - } - } - else cursor.insRightOf(thisDir); + cursor.insRightOf(pt[L] as MQNode); + } else { + cursor.insAtLeftEnd(pt.parent); + } + } else cursor.insRightOf(thisDir); } break; } } - }; - finalizeTree () { + } + finalizeTree() { var endsL = this.ends[L] as MQNode; - endsL.write = function(cursor:Cursor, ch:string) { - if (cursor.options.autoSubscriptNumerals && this === (this.parent as SupSub).sub) { + endsL.write = function (cursor: Cursor, ch: string) { + if ( + cursor.options.autoSubscriptNumerals && + this === (this.parent as SupSub).sub + ) { if (ch === '_') return; var cmd = this.chToCmd(ch, cursor.options); if (cmd instanceof MQSymbol) cursor.deleteSelection(); else cursor.clearSelection().insRightOf(this.parent); cmd.createLeftOf(cursor.show()); - cursor.controller.aria.queue('Baseline').alert(cmd.mathspeak({ createdLeftOf: cursor })); + cursor.controller.aria + .queue('Baseline') + .alert(cmd.mathspeak({ createdLeftOf: cursor })); return; } - if (cursor[L] && !cursor[R] && !cursor.selection - && cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1) { + if ( + cursor[L] && + !cursor[R] && + !cursor.selection && + cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1 + ) { cursor.insRightOf(this.parent); cursor.controller.aria.queue('Baseline'); } MathBlock.prototype.write.call(this, cursor, ch); }; - }; - moveTowards (dir:Direction, cursor:Cursor, updown?:'up'|'down') { + } + moveTowards(dir: Direction, cursor: Cursor, updown?: 'up' | 'down') { if (cursor.options.autoSubscriptNumerals && !this.sup) { cursor.insDirOf(dir, this); - } - else super.moveTowards(dir, cursor, updown); - }; - deleteTowards (dir:Direction, cursor:Cursor) { + } else super.moveTowards(dir, cursor, updown); + } + deleteTowards(dir: Direction, cursor: Cursor) { if (cursor.options.autoSubscriptNumerals && this.sub) { var cmd = this.sub.ends[-dir as Direction]; if (cmd instanceof MQSymbol) cmd.remove(); - else if (cmd) cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir as Direction, this.sub)); + else if (cmd) + cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir as Direction, this.sub)); // TODO: factor out a .removeBlock() or something if (this.sub.isEmpty()) { @@ -327,70 +401,92 @@ class SupSub extends MathCommand { // to delete the 1 but to end up rightward of x^2; with non-negated // `dir` (try it), the cursor appears to have gone "through" the ^2. } - } - else super.deleteTowards(dir, cursor) - }; - latex () { - function latex(prefix:string, block:NodeRef | undefined) { + } else super.deleteTowards(dir, cursor); + } + latex() { + function latex(prefix: string, block: NodeRef | undefined) { var l = block && block.latex(); return block ? prefix + '{' + (l || ' ') + '}' : ''; } return latex('_', this.sub) + latex('^', this.sup); - }; - text () { - function text(prefix:string, block:NodeRef | undefined) { + } + text() { + function text(prefix: string, block: NodeRef | undefined) { var l = (block && block.text()) || ''; - return block ? prefix + (l.length === 1 ? l : '(' + (l || ' ') + ')') : ''; + return block + ? prefix + (l.length === 1 ? l : '(' + (l || ' ') + ')') + : ''; } return text('_', this.sub) + text('^', this.sup); - }; - addBlock (block:MathBlock) { + } + addBlock(block: MathBlock) { if (this.supsub === 'sub') { this.sup = this.upInto = (this.sub as MQNode).upOutOf = block; - block.adopt(this, (this.sub as MQNode), 0).downOutOf = this.sub; - block.jQ = $('').append(block.jQ.children()).prependTo(this.jQ); + block.adopt(this, this.sub as MQNode, 0).downOutOf = this.sub; + block.jQ = $('') + .append(block.jQ.children()) + .prependTo(this.jQ); NodeBase.linkElementByBlockNode(block.jQ[0], block); - } - else { + } else { this.sub = this.downInto = (this.sup as MQNode).downOutOf = block; - block.adopt(this, 0, (this.sup as MQNode)).upOutOf = this.sup; - block.jQ = $('').append(block.jQ.children()) + block.adopt(this, 0, this.sup as MQNode).upOutOf = this.sup; + block.jQ = $('') + .append(block.jQ.children()) .appendTo(this.jQ.removeClass('mq-sup-only')); NodeBase.linkElementByBlockNode(block.jQ[0], block); - this.jQ.append(''); + this.jQ.append( + '' + ); } - // like 'sub sup'.split(' ').forEach(function(supsub) { ... }); - for (var i = 0; i < 2; i += 1) (function(cmd:SupSub, supsub:'sup'|'sub', oppositeSupsub:'sup'|'sub', updown:'up'|'down') { - const cmdSubSub = cmd[supsub]!; - cmdSubSub.deleteOutOf = function(dir:Direction, cursor:Cursor) { - cursor.insDirOf((this[dir] ? (-dir as Direction) : dir), this.parent); - if (!this.isEmpty()) { - var end = this.ends[dir]; - this.children().disown() - .withDirAdopt(dir, cursor.parent, cursor[dir] as MQNode, cursor[-dir as Direction] as NodeRef) - .jQ.insDirOf(-dir as Direction, cursor.jQ); - cursor[-dir as Direction] = end; - } - cmd.supsub = oppositeSupsub; - delete cmd[supsub]; - delete cmd[`${updown}Into`]; - const cmdOppositeSupsub = cmd[oppositeSupsub]!; - cmdOppositeSupsub[`${updown}OutOf`] = insLeftOfMeUnlessAtEnd; - delete (cmdOppositeSupsub as any).deleteOutOf; // TODO - refactor so this method can be optional - if (supsub === 'sub') $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove(); - this.remove(); - }; - }(this, 'sub sup'.split(' ')[i] as 'sup'|'sup', 'sup sub'.split(' ')[i] as 'sup'|'sup', 'down up'.split(' ')[i] as 'up' | 'down')); - }; -}; + for (var i = 0; i < 2; i += 1) + (function ( + cmd: SupSub, + supsub: 'sup' | 'sub', + oppositeSupsub: 'sup' | 'sub', + updown: 'up' | 'down' + ) { + const cmdSubSub = cmd[supsub]!; + cmdSubSub.deleteOutOf = function (dir: Direction, cursor: Cursor) { + cursor.insDirOf(this[dir] ? (-dir as Direction) : dir, this.parent); + if (!this.isEmpty()) { + var end = this.ends[dir]; + this.children() + .disown() + .withDirAdopt( + dir, + cursor.parent, + cursor[dir] as MQNode, + cursor[-dir as Direction] as NodeRef + ) + .jQ.insDirOf(-dir as Direction, cursor.jQ); + cursor[-dir as Direction] = end; + } + cmd.supsub = oppositeSupsub; + delete cmd[supsub]; + delete cmd[`${updown}Into`]; + const cmdOppositeSupsub = cmd[oppositeSupsub]!; + cmdOppositeSupsub[`${updown}OutOf`] = insLeftOfMeUnlessAtEnd; + delete (cmdOppositeSupsub as any).deleteOutOf; // TODO - refactor so this method can be optional + if (supsub === 'sub') + $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove(); + this.remove(); + }; + })( + this, + 'sub sup'.split(' ')[i] as 'sup' | 'sup', + 'sup sub'.split(' ')[i] as 'sup' | 'sup', + 'down up'.split(' ')[i] as 'up' | 'down' + ); + } +} -function insLeftOfMeUnlessAtEnd(this:MQNode, cursor:Cursor) { +function insLeftOfMeUnlessAtEnd(this: MQNode, cursor: Cursor) { // cursor.insLeftOf(cmd), unless cursor at the end of block, and every // ancestor cmd is at the end of every ancestor block var cmd = this.parent; - var ancestorCmd:MQNode|Anticursor|Cursor = cursor; + var ancestorCmd: MQNode | Anticursor | Cursor = cursor; do { if (ancestorCmd[R]) return cursor.insLeftOf(cmd); ancestorCmd = ancestorCmd.parent.parent; @@ -401,466 +497,501 @@ function insLeftOfMeUnlessAtEnd(this:MQNode, cursor:Cursor) { class SubscriptCommand extends SupSub { supsub = 'sub' as const; - + htmlTemplate = - '' - + '&0' - + '' - + '' - - textTemplate = [ '_' ]; - - mathspeakTemplate = [ 'Subscript,', ', Baseline']; - + '' + + '&0' + + '' + + ''; + + textTemplate = ['_']; + + mathspeakTemplate = ['Subscript,', ', Baseline']; + ariaLabel = 'subscript'; - - finalizeTree () { + + finalizeTree() { this.downInto = this.sub = this.ends[L] as MathBlock; this.sub.upOutOf = insLeftOfMeUnlessAtEnd; - super.finalizeTree() - }; -}; -LatexCmds.subscript = -LatexCmds._ = SubscriptCommand; + super.finalizeTree(); + } +} +LatexCmds.subscript = LatexCmds._ = SubscriptCommand; LatexCmds.superscript = -LatexCmds.supscript = -LatexCmds['^'] = class SuperscriptCommand extends SupSub { - supsub = 'sup' as const; - - htmlTemplate = - '' - + '&0' - + '' - ; - textTemplate = ['^(', ')']; - mathspeak (opts?:MathspeakOptions) { - // Simplify basic exponent speech for common whole numbers. - var child = this.upInto; - if (child !== undefined) { - // Calculate this item's inner text to determine whether to shorten the returned speech. - // Do not calculate its inner mathspeak now until we know that the speech is to be truncated. - // Since the mathspeak computation is recursive, we want to call it only once in this function to avoid performance bottlenecks. - var innerText = getCtrlSeqsFromBlock(child); - // If the superscript is a whole number, shorten the speech that is returned. - if ( - (!opts || !opts.ignoreShorthand) && - intRgx.test(innerText) - ) { - // Simple cases - if (innerText === '0') { - return 'to the 0 power'; - } else if (innerText === '2') { - return 'squared'; - } else if (innerText === '3') { - return 'cubed'; - } - - // More complex cases. - var suffix = ''; - // Limit suffix addition to exponents < 1000. - if (/^[+-]?\d{1,3}$/.test(innerText)) { - if (/(11|12|13|4|5|6|7|8|9|0)$/.test(innerText)) { - suffix = 'th'; - } else if (/1$/.test(innerText)) { - suffix = 'st'; - } else if (/2$/.test(innerText)) { - suffix = 'nd'; - } else if (/3$/.test(innerText)) { - suffix = 'rd'; + LatexCmds.supscript = + LatexCmds['^'] = + class SuperscriptCommand extends SupSub { + supsub = 'sup' as const; + + htmlTemplate = + '' + + '&0' + + ''; + textTemplate = ['^(', ')']; + mathspeak(opts?: MathspeakOptions) { + // Simplify basic exponent speech for common whole numbers. + var child = this.upInto; + if (child !== undefined) { + // Calculate this item's inner text to determine whether to shorten the returned speech. + // Do not calculate its inner mathspeak now until we know that the speech is to be truncated. + // Since the mathspeak computation is recursive, we want to call it only once in this function to avoid performance bottlenecks. + var innerText = getCtrlSeqsFromBlock(child); + // If the superscript is a whole number, shorten the speech that is returned. + if ((!opts || !opts.ignoreShorthand) && intRgx.test(innerText)) { + // Simple cases + if (innerText === '0') { + return 'to the 0 power'; + } else if (innerText === '2') { + return 'squared'; + } else if (innerText === '3') { + return 'cubed'; + } + + // More complex cases. + var suffix = ''; + // Limit suffix addition to exponents < 1000. + if (/^[+-]?\d{1,3}$/.test(innerText)) { + if (/(11|12|13|4|5|6|7|8|9|0)$/.test(innerText)) { + suffix = 'th'; + } else if (/1$/.test(innerText)) { + suffix = 'st'; + } else if (/2$/.test(innerText)) { + suffix = 'nd'; + } else if (/3$/.test(innerText)) { + suffix = 'rd'; + } + } + var innerMathspeak = + typeof child === 'object' ? child.mathspeak() : innerText; + return 'to the ' + innerMathspeak + suffix + ' power'; } } - var innerMathspeak = typeof(child) === 'object' - ? child.mathspeak() - : innerText; - return 'to the ' + innerMathspeak + suffix + ' power'; + return super.mathspeak(); } - } - return super.mathspeak(); - }; - ariaLabel = 'superscript'; - mathspeakTemplate = [ 'Superscript,', ', Baseline']; - finalizeTree () { - this.upInto = this.sup = this.ends[R] as MathBlock; - this.sup.downOutOf = insLeftOfMeUnlessAtEnd; - super.finalizeTree(); - }; -}; + ariaLabel = 'superscript'; + mathspeakTemplate = ['Superscript,', ', Baseline']; + finalizeTree() { + this.upInto = this.sup = this.ends[R] as MathBlock; + this.sup.downOutOf = insLeftOfMeUnlessAtEnd; + super.finalizeTree(); + } + }; class SummationNotation extends MathCommand { - - constructor (ch:string, html:string, ariaLabel?:string) { + constructor(ch: string, html: string, ariaLabel?: string) { super(); this.ariaLabel = ariaLabel || ch.replace(/^\\/, ''); var htmlTemplate = - '' - + '&1' - + ''+html+'' - + '&0' - + '' - ; - - MQSymbol.prototype.setCtrlSeqHtmlTextAndMathspeak.call(this, ch, htmlTemplate); - }; - createLeftOf (cursor:Cursor) { + '' + + '&1' + + '' + + html + + '' + + '&0' + + ''; + MQSymbol.prototype.setCtrlSeqHtmlTextAndMathspeak.call( + this, + ch, + htmlTemplate + ); + } + createLeftOf(cursor: Cursor) { super.createLeftOf(cursor); if (cursor.options.sumStartsWithNEquals) { new Letter('n').createLeftOf(cursor); new Equality().createLeftOf(cursor); } - }; - latex () { - function simplify(latex:string) { + } + latex() { + function simplify(latex: string) { return '{' + (latex || ' ') + '}'; } - return this.ctrlSeq + '_' + simplify((this.ends[L] as MQNode).latex()) + - '^' + simplify((this.ends[R] as MQNode).latex()); - }; - mathspeak () { - return 'Start ' + this.ariaLabel + ' from ' + (this.ends[L] as MQNode).mathspeak() + - ' to ' + (this.ends[R] as MQNode).mathspeak() + ', end ' + this.ariaLabel + ', '; - }; - parser () { + return ( + this.ctrlSeq + + '_' + + simplify((this.ends[L] as MQNode).latex()) + + '^' + + simplify((this.ends[R] as MQNode).latex()) + ); + } + mathspeak() { + return ( + 'Start ' + + this.ariaLabel + + ' from ' + + (this.ends[L] as MQNode).mathspeak() + + ' to ' + + (this.ends[R] as MQNode).mathspeak() + + ', end ' + + this.ariaLabel + + ', ' + ); + } + parser() { var string = Parser.string; var optWhitespace = Parser.optWhitespace; var succeed = Parser.succeed; var block = latexMathParser.block; var self = this; - var blocks = self.blocks = [ new MathBlock(), new MathBlock() ]; + var blocks = (self.blocks = [new MathBlock(), new MathBlock()]); for (var i = 0; i < blocks.length; i += 1) { blocks[i].adopt(self, self.ends[R], 0); } - return optWhitespace.then(string('_').or(string('^'))).then(function(supOrSub) { - var child = blocks[supOrSub === '_' ? 0 : 1]; - return block.then(function(block) { - block.children().adopt(child, child.ends[R], 0); - return succeed(self); - }); - }).many().result(self); - }; - finalizeTree () { + return optWhitespace + .then(string('_').or(string('^'))) + .then(function (supOrSub) { + var child = blocks[supOrSub === '_' ? 0 : 1]; + return block.then(function (block) { + block.children().adopt(child, child.ends[R], 0); + return succeed(self); + }); + }) + .many() + .result(self); + } + finalizeTree() { var endsL = this.ends[L] as MQNode; var endsR = this.ends[R] as MQNode; endsL.ariaLabel = 'lower bound'; endsR.ariaLabel = 'upper bound'; - this.downInto = endsL + this.downInto = endsL; this.upInto = endsR; endsL.upOutOf = endsR; endsR.downOutOf = endsL; - }; -}; + } +} LatexCmds['∑'] = -LatexCmds.sum = -LatexCmds.summation = () => new SummationNotation('\\sum ','∑', 'sum'); + LatexCmds.sum = + LatexCmds.summation = + () => new SummationNotation('\\sum ', '∑', 'sum'); LatexCmds['∏'] = -LatexCmds.prod = -LatexCmds.product = () => new SummationNotation('\\prod ','∏', 'product'); + LatexCmds.prod = + LatexCmds.product = + () => new SummationNotation('\\prod ', '∏', 'product'); -LatexCmds.coprod = -LatexCmds.coproduct = () => new SummationNotation('\\coprod ','∐', 'co product'); +LatexCmds.coprod = LatexCmds.coproduct = () => + new SummationNotation('\\coprod ', '∐', 'co product'); LatexCmds['∫'] = -LatexCmds['int'] = -LatexCmds.integral = class extends SummationNotation { - constructor () { - var htmlTemplate = - '' - + '' - + '' - + '&1' - + '&0' - + '' - + '' - + '' - ; - - super('\\int ', '', 'integral'); - - this.ariaLabel = 'integral'; - this.htmlTemplate = htmlTemplate; - }; - - createLeftOf (cursor:Cursor) { - // FIXME: refactor rather than overriding - MathCommand.prototype.createLeftOf.call(this, cursor); - } -}; -var Fraction = -LatexCmds.frac = -LatexCmds.dfrac = -LatexCmds.cfrac = -LatexCmds.fraction = class FracNode extends MathCommand { - ctrlSeq = '\\frac'; - htmlTemplate = - '' - + '&0' - + '&1' - + '' - + '' - ; - textTemplate = ['(', ')/(', ')']; - finalizeTree () { - const endsL = this.ends[L] as MQNode; - const endsR = this.ends[R] as MQNode; - this.upInto = endsR.upOutOf = endsL; - this.downInto = endsL.downOutOf = endsR; - endsL.ariaLabel = 'numerator'; - endsR.ariaLabel = 'denominator'; - if(this.getFracDepth() > 1) { - this.mathspeakTemplate = ['StartNestedFraction,', 'NestedOver', ', EndNestedFraction']; - } else { - this.mathspeakTemplate = ['StartFraction,', 'Over', ', EndFraction']; - } - }; - - mathspeak (opts?:MathspeakOptions) { - if (opts && opts.createdLeftOf) { - var cursor = opts.createdLeftOf; - return cursor.parent.mathspeak(); - } - - var numText = getCtrlSeqsFromBlock(this.ends[L]); - var denText = getCtrlSeqsFromBlock(this.ends[R]); + LatexCmds['int'] = + LatexCmds.integral = + class extends SummationNotation { + constructor() { + var htmlTemplate = + '' + + '' + + '' + + '&1' + + '&0' + + '' + + '' + + ''; + super('\\int ', '', 'integral'); + + this.ariaLabel = 'integral'; + this.htmlTemplate = htmlTemplate; + } - // Shorten mathspeak value for whole number fractions whose denominator is less than 10. - if ( - (!opts || !opts.ignoreShorthand) && - intRgx.test(numText) && intRgx.test(denText) - ) { - var isSingular = numText === '1' || numText === '-1'; - var newDenSpeech = ''; - if (denText === '2') { - newDenSpeech = isSingular - ? 'half' - : 'halves'; - } else if (denText === '3') { - newDenSpeech = isSingular - ? 'third' - : 'thirds'; - } else if (denText === '4') { - newDenSpeech = isSingular - ? 'quarter' - : 'quarters'; - } else if (denText === '5') { - newDenSpeech = isSingular - ? 'fifth' - : 'fifths'; - } else if (denText === '6') { - newDenSpeech = isSingular - ? 'sixth' - : 'sixths'; - } else if (denText === '7') { - newDenSpeech = isSingular - ? 'seventh' - : 'sevenths'; - } else if (denText === '8') { - newDenSpeech = isSingular - ? 'eighth' - : 'eighths'; - } else if (denText === '9') { - newDenSpeech = isSingular - ? 'ninth' - : 'ninths'; + createLeftOf(cursor: Cursor) { + // FIXME: refactor rather than overriding + MathCommand.prototype.createLeftOf.call(this, cursor); } - if (newDenSpeech !== '') { - var output = ''; - // Handle the case of an integer followed by a simplified fraction such as 1\frac{1}{2}. - // Such combinations should be spoken aloud as "1 and 1 half." - // Start at the left sibling of the fraction and continue leftward until something other than a digit or whitespace is found. - var precededByInteger = false; - for (var sibling:NodeRef | undefined = this[L]; sibling && sibling[L] !== undefined; sibling = sibling[L]) { - // Ignore whitespace - if (sibling.ctrlSeq === '\\ ') { - continue; - } else if (intRgx.test(sibling.ctrlSeq || '')) { - precededByInteger = true; - } else { - precededByInteger = false; - break; - } - } - if (precededByInteger) { - output += 'and '; + }; +var Fraction = + (LatexCmds.frac = + LatexCmds.dfrac = + LatexCmds.cfrac = + LatexCmds.fraction = + class FracNode extends MathCommand { + ctrlSeq = '\\frac'; + htmlTemplate = + '' + + '&0' + + '&1' + + '' + + ''; + textTemplate = ['(', ')/(', ')']; + finalizeTree() { + const endsL = this.ends[L] as MQNode; + const endsR = this.ends[R] as MQNode; + this.upInto = endsR.upOutOf = endsL; + this.downInto = endsL.downOutOf = endsR; + endsL.ariaLabel = 'numerator'; + endsR.ariaLabel = 'denominator'; + if (this.getFracDepth() > 1) { + this.mathspeakTemplate = [ + 'StartNestedFraction,', + 'NestedOver', + ', EndNestedFraction', + ]; + } else { + this.mathspeakTemplate = ['StartFraction,', 'Over', ', EndFraction']; } - output += (this.ends[L] as MQNode).mathspeak() + ' ' + newDenSpeech; - return output; } - } - return super.mathspeak(); - }; + mathspeak(opts?: MathspeakOptions) { + if (opts && opts.createdLeftOf) { + var cursor = opts.createdLeftOf; + return cursor.parent.mathspeak(); + } - getFracDepth () { - var level = 0; - var walkUp = function(item:NodeRef, level:number):number { - if(item instanceof MQNode && item.ctrlSeq && item.ctrlSeq.toLowerCase().search('frac') >= 0) level += 1; - if(item && item.parent) return walkUp(item.parent, level); - else return level; - }; - return walkUp(this, level); - }; -}; + var numText = getCtrlSeqsFromBlock(this.ends[L]); + var denText = getCtrlSeqsFromBlock(this.ends[R]); + + // Shorten mathspeak value for whole number fractions whose denominator is less than 10. + if ( + (!opts || !opts.ignoreShorthand) && + intRgx.test(numText) && + intRgx.test(denText) + ) { + var isSingular = numText === '1' || numText === '-1'; + var newDenSpeech = ''; + if (denText === '2') { + newDenSpeech = isSingular ? 'half' : 'halves'; + } else if (denText === '3') { + newDenSpeech = isSingular ? 'third' : 'thirds'; + } else if (denText === '4') { + newDenSpeech = isSingular ? 'quarter' : 'quarters'; + } else if (denText === '5') { + newDenSpeech = isSingular ? 'fifth' : 'fifths'; + } else if (denText === '6') { + newDenSpeech = isSingular ? 'sixth' : 'sixths'; + } else if (denText === '7') { + newDenSpeech = isSingular ? 'seventh' : 'sevenths'; + } else if (denText === '8') { + newDenSpeech = isSingular ? 'eighth' : 'eighths'; + } else if (denText === '9') { + newDenSpeech = isSingular ? 'ninth' : 'ninths'; + } + if (newDenSpeech !== '') { + var output = ''; + // Handle the case of an integer followed by a simplified fraction such as 1\frac{1}{2}. + // Such combinations should be spoken aloud as "1 and 1 half." + // Start at the left sibling of the fraction and continue leftward until something other than a digit or whitespace is found. + var precededByInteger = false; + for ( + var sibling: NodeRef | undefined = this[L]; + sibling && sibling[L] !== undefined; + sibling = sibling[L] + ) { + // Ignore whitespace + if (sibling.ctrlSeq === '\\ ') { + continue; + } else if (intRgx.test(sibling.ctrlSeq || '')) { + precededByInteger = true; + } else { + precededByInteger = false; + break; + } + } + if (precededByInteger) { + output += 'and '; + } + output += (this.ends[L] as MQNode).mathspeak() + ' ' + newDenSpeech; + return output; + } + } -var LiveFraction = -LatexCmds.over = -CharCmds['/'] = class extends Fraction { - createLeftOf (cursor:Cursor) { - if (!this.replacedFragment) { - var leftward = cursor[L]; - - if (!cursor.options.typingSlashCreatesNewFraction) { - while (leftward && - !( - leftward instanceof BinaryOperator || - leftward instanceof (LatexCmds.text || noop) || - leftward instanceof SummationNotation || - leftward.ctrlSeq === '\\ ' || - /^[,;:]$/.test(leftward.ctrlSeq as string) - ) //lookbehind for operator - ) leftward = leftward[L]; + return super.mathspeak(); } - if (leftward instanceof SummationNotation && leftward[R] instanceof SupSub) { - leftward = leftward[R] as MQNode; - let leftwardR = leftward[R]; - if (leftwardR instanceof SupSub && leftwardR.ctrlSeq != leftward.ctrlSeq) - leftward = leftward[R]; + + getFracDepth() { + var level = 0; + var walkUp = function (item: NodeRef, level: number): number { + if ( + item instanceof MQNode && + item.ctrlSeq && + item.ctrlSeq.toLowerCase().search('frac') >= 0 + ) + level += 1; + if (item && item.parent) return walkUp(item.parent, level); + else return level; + }; + return walkUp(this, level); } + }); + +var LiveFraction = + (LatexCmds.over = + CharCmds['/'] = + class extends Fraction { + createLeftOf(cursor: Cursor) { + if (!this.replacedFragment) { + var leftward = cursor[L]; + + if (!cursor.options.typingSlashCreatesNewFraction) { + while ( + leftward && + !( + leftward instanceof BinaryOperator || + leftward instanceof (LatexCmds.text || noop) || + leftward instanceof SummationNotation || + leftward.ctrlSeq === '\\ ' || + /^[,;:]$/.test(leftward.ctrlSeq as string) + ) //lookbehind for operator + ) + leftward = leftward[L]; + } + if ( + leftward instanceof SummationNotation && + leftward[R] instanceof SupSub + ) { + leftward = leftward[R] as MQNode; + let leftwardR = leftward[R]; + if ( + leftwardR instanceof SupSub && + leftwardR.ctrlSeq != leftward.ctrlSeq + ) + leftward = leftward[R]; + } - if (leftward !== cursor[L] && !cursor.isTooDeep(1)) { - let leftwardR = (leftward as MQNode)[R] as MQNode; - let cursorL = cursor[L] as MQNode; + if (leftward !== cursor[L] && !cursor.isTooDeep(1)) { + let leftwardR = (leftward as MQNode)[R] as MQNode; + let cursorL = cursor[L] as MQNode; - this.replaces(new Fragment(leftwardR || cursor.parent.ends[L], cursorL)); - cursor[L] = leftward; + this.replaces( + new Fragment(leftwardR || cursor.parent.ends[L], cursorL) + ); + cursor[L] = leftward; + } + } + super.createLeftOf(cursor); } - } - super.createLeftOf(cursor); - }; -}; + }); -const AnsBuilder = () => new MQSymbol( - '\\operatorname{ans}', - 'ans', - 'ans' - ); +const AnsBuilder = () => + new MQSymbol('\\operatorname{ans}', 'ans', 'ans'); LatexCmds.ans = AnsBuilder; -const PercentOfBuilder = () => new MQSymbol( - '\\%\\operatorname{of}', - '% of ', - 'percent of' - ) -LatexCmds.percent = -LatexCmds.percentof = PercentOfBuilder +const PercentOfBuilder = () => + new MQSymbol( + '\\%\\operatorname{of}', + '% of ', + 'percent of' + ); +LatexCmds.percent = LatexCmds.percentof = PercentOfBuilder; class SquareRoot extends MathCommand { ctrlSeq = '\\sqrt'; htmlTemplate = - '' - + '' - + SVG_SYMBOLS.sqrt.html - + '' - + '&0' - + '' - ; + '' + + '' + + SVG_SYMBOLS.sqrt.html + + '' + + '&0' + + ''; textTemplate = ['sqrt(', ')']; mathspeakTemplate = ['StartRoot,', ', EndRoot']; ariaLabel = 'root'; - parser () { - return latexMathParser.optBlock.then(function(optBlock) { - return latexMathParser.block.map(function(block) { - var nthroot = new NthRoot(); - nthroot.blocks = [ optBlock, block ]; - optBlock.adopt(nthroot, 0, 0); - block.adopt(nthroot, optBlock, 0); - return nthroot; - }); - }).or(super.parser()); - }; -}; + parser() { + return latexMathParser.optBlock + .then(function (optBlock) { + return latexMathParser.block.map(function (block) { + var nthroot = new NthRoot(); + nthroot.blocks = [optBlock, block]; + optBlock.adopt(nthroot, 0, 0); + block.adopt(nthroot, optBlock, 0); + return nthroot; + }); + }) + .or(super.parser()); + } +} LatexCmds.sqrt = SquareRoot; LatexCmds.hat = class Hat extends MathCommand { ctrlSeq = '\\hat'; htmlTemplate = - '' - + '^' - + '&0' - + '' - ; + '' + + '^' + + '&0' + + ''; textTemplate = ['hat(', ')']; }; class NthRoot extends SquareRoot { htmlTemplate = - '' - + '&0' - + '' - + '' - + SVG_SYMBOLS.sqrt.html - + '' - + '&1' - + '' - + '' - ; + '' + + '&0' + + '' + + '' + + SVG_SYMBOLS.sqrt.html + + '' + + '&1' + + '' + + ''; textTemplate = ['sqrt[', '](', ')']; - latex () { - return '\\sqrt['+(this.ends[L] as MQNode).latex()+']{'+(this.ends[R] as MQNode).latex()+'}'; - }; - mathspeak () { + latex() { + return ( + '\\sqrt[' + + (this.ends[L] as MQNode).latex() + + ']{' + + (this.ends[R] as MQNode).latex() + + '}' + ); + } + mathspeak() { var indexMathspeak = (this.ends[L] as MQNode).mathspeak(); var radicandMathspeak = (this.ends[R] as MQNode).mathspeak(); (this.ends[L] as MQNode).ariaLabel = 'Index'; (this.ends[R] as MQNode).ariaLabel = 'Radicand'; - if (indexMathspeak === '3') { // cube root - return 'Start Cube Root, '+radicandMathspeak+', End Cube Root'; + if (indexMathspeak === '3') { + // cube root + return 'Start Cube Root, ' + radicandMathspeak + ', End Cube Root'; } else { - return 'Root Index '+indexMathspeak+', Start Root, '+radicandMathspeak+', End Root'; + return ( + 'Root Index ' + + indexMathspeak + + ', Start Root, ' + + radicandMathspeak + + ', End Root' + ); } - }; -}; + } +} LatexCmds.nthroot = NthRoot; LatexCmds.cbrt = class extends NthRoot { - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { super.createLeftOf(cursor); new Digit('3').createLeftOf(cursor); cursor.controller.moveRight(); - }; + } }; class DiacriticAbove extends MathCommand { - constructor (ctrlSeq:string, symbol:string, textTemplate?:string[]) { + constructor(ctrlSeq: string, symbol: string, textTemplate?: string[]) { var htmlTemplate = - '' - + ''+symbol+'' - + '&0' - + '' - ; - + '' + + '' + + symbol + + '' + + '&0' + + ''; super(ctrlSeq, htmlTemplate, textTemplate); - }; -}; + } +} LatexCmds.vec = () => new DiacriticAbove('\\vec', '→', ['vec(', ')']); LatexCmds.tilde = () => new DiacriticAbove('\\tilde', '~', ['tilde(', ')']); class DelimsNode extends MathCommand { - delimjQs:$; - contentjQ:$; + delimjQs: $; + contentjQ: $; - jQadd (el:$) { + jQadd(el: $) { super.jQadd(el); this.delimjQs = this.jQ.children(':first').add(this.jQ.children(':last')); this.contentjQ = this.jQ.children(':eq(1)'); return this.jQ; - }; + } } // Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces) @@ -868,173 +999,277 @@ class DelimsNode extends MathCommand { // far end of current block, until you type an opposing one class Bracket extends DelimsNode { side: BracketSide; - sides:{ - [L]: {ch:string, ctrlSeq:string}, - [R]: {ch:string, ctrlSeq:string} - } - constructor (side:BracketSide, open:string, close:string, ctrlSeq:string, end:string) { - super('\\left'+ctrlSeq, undefined, [open, close]); + sides: { + [L]: { ch: string; ctrlSeq: string }; + [R]: { ch: string; ctrlSeq: string }; + }; + constructor( + side: BracketSide, + open: string, + close: string, + ctrlSeq: string, + end: string + ) { + super('\\left' + ctrlSeq, undefined, [open, close]); this.side = side; this.sides = { [L]: { ch: open, ctrlSeq: ctrlSeq }, - [R]: { ch: close, ctrlSeq: end } + [R]: { ch: close, ctrlSeq: end }, }; - }; - numBlocks () { return 1; }; - html () { + } + numBlocks() { + return 1; + } + html() { var leftSymbol = this.getSymbol(L); var rightSymbol = this.getSymbol(R); - // wait until now so that .side may + // wait until now so that .side may this.htmlTemplate = // be set by createLeftOf or parser - '' - + '' - + leftSymbol.html - + '' - + '&0' - + '' - + rightSymbol.html - + '' - + '' - ; + '' + + '' + + leftSymbol.html + + '' + + '&0' + + '' + + rightSymbol.html + + '' + + ''; return super.html(); - }; - getSymbol (side:BracketSide) { - var ch = (this.sides[side || R].ch) as keyof typeof SVG_SYMBOLS; - return SVG_SYMBOLS[ch] || {width: '0', html: ''}; - }; - latex () { - return '\\left'+this.sides[L].ctrlSeq+(this.ends[L] as MQNode).latex()+'\\right'+this.sides[R].ctrlSeq; - }; - mathspeak (opts?:MathspeakOptions) { - var open = this.sides[L].ch, close = this.sides[R].ch; + } + getSymbol(side: BracketSide) { + var ch = this.sides[side || R].ch as keyof typeof SVG_SYMBOLS; + return SVG_SYMBOLS[ch] || { width: '0', html: '' }; + } + latex() { + return ( + '\\left' + + this.sides[L].ctrlSeq + + (this.ends[L] as MQNode).latex() + + '\\right' + + this.sides[R].ctrlSeq + ); + } + mathspeak(opts?: MathspeakOptions) { + var open = this.sides[L].ch, + close = this.sides[R].ch; if (open === '|' && close === '|') { this.mathspeakTemplate = ['StartAbsoluteValue,', ', EndAbsoluteValue']; this.ariaLabel = 'absolute value'; - } - else if (opts && opts.createdLeftOf && this.side) { + } else if (opts && opts.createdLeftOf && this.side) { var ch = ''; if (this.side === L) ch = this.textTemplate[0]; else if (this.side === R) ch = this.textTemplate[1]; - return (this.side === L ? 'left ' : 'right ') + BRACKET_NAMES[ch as keyof typeof BRACKET_NAMES]; - } - else { - this.mathspeakTemplate = ['left ' + BRACKET_NAMES[open as keyof typeof BRACKET_NAMES]+',', ', right ' + BRACKET_NAMES[close as keyof typeof BRACKET_NAMES]]; - this.ariaLabel = BRACKET_NAMES[open as keyof typeof BRACKET_NAMES]+' block'; + return ( + (this.side === L ? 'left ' : 'right ') + + BRACKET_NAMES[ch as keyof typeof BRACKET_NAMES] + ); + } else { + this.mathspeakTemplate = [ + 'left ' + BRACKET_NAMES[open as keyof typeof BRACKET_NAMES] + ',', + ', right ' + BRACKET_NAMES[close as keyof typeof BRACKET_NAMES], + ]; + this.ariaLabel = + BRACKET_NAMES[open as keyof typeof BRACKET_NAMES] + ' block'; } return super.mathspeak(); - }; - matchBrack (opts:CursorOptions, expectedSide:BracketSide, node:NodeRef | undefined) { + } + matchBrack( + opts: CursorOptions, + expectedSide: BracketSide, + node: NodeRef | undefined + ) { // return node iff it's a matching 1-sided bracket of expected side (if any) - return node instanceof Bracket && node.side && node.side !== -expectedSide - && (!opts.restrictMismatchedBrackets - || OPP_BRACKS[this.sides[this.side as Direction].ch as keyof typeof BRACKET_NAMES] === node.sides[node.side].ch - || { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && node; - }; - closeOpposing (brack:Bracket) { + return ( + node instanceof Bracket && + node.side && + node.side !== -expectedSide && + (!opts.restrictMismatchedBrackets || + OPP_BRACKS[ + this.sides[this.side as Direction].ch as keyof typeof BRACKET_NAMES + ] === node.sides[node.side].ch || + { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && + node + ); + } + closeOpposing(brack: Bracket) { brack.side = 0; brack.sides[this.side as Direction] = this.sides[this.side as Direction]; // copy over my info (may be - var $brack = brack.delimjQs.eq(this.side === L ? 0 : 1) // mismatched, like [a, b)) + var $brack = brack.delimjQs + .eq(this.side === L ? 0 : 1) // mismatched, like [a, b)) .removeClass('mq-ghost'); this.replaceBracket($brack, this.side); - }; - createLeftOf (cursor:Cursor) { + } + createLeftOf(cursor: Cursor) { var brack; - if (!this.replacedFragment) { // unless wrapping seln in brackets, - // check if next to or inside an opposing one-sided bracket + if (!this.replacedFragment) { + // unless wrapping seln in brackets, + // check if next to or inside an opposing one-sided bracket var opts = cursor.options; - if (this.sides[L].ch === '|') { // check both sides if I'm a pipe - brack = this.matchBrack(opts, R, cursor[R]) - || this.matchBrack(opts, L, cursor[L]) - || this.matchBrack(opts, 0, cursor.parent.parent); - } - else { - brack = this.matchBrack(opts, -this.side as BracketSide, cursor[-this.side as Direction]) - || this.matchBrack(opts, -this.side as BracketSide, cursor.parent.parent); + if (this.sides[L].ch === '|') { + // check both sides if I'm a pipe + brack = + this.matchBrack(opts, R, cursor[R]) || + this.matchBrack(opts, L, cursor[L]) || + this.matchBrack(opts, 0, cursor.parent.parent); + } else { + brack = + this.matchBrack( + opts, + -this.side as BracketSide, + cursor[-this.side as Direction] + ) || + this.matchBrack( + opts, + -this.side as BracketSide, + cursor.parent.parent + ); } } if (brack) { - var side = this.side = -brack.side as BracketSide; // may be pipe with .side not yet set + var side = (this.side = -brack.side as BracketSide); // may be pipe with .side not yet set this.closeOpposing(brack); - if (brack === cursor.parent.parent && cursor[side as Direction]) { // move the stuff between - new Fragment(cursor[side as Direction] as MQNode, cursor.parent.ends[side as Direction] as MQNode, -side as Direction) // me and ghost outside - .disown().withDirAdopt(-side as Direction, brack.parent, brack, brack[side as Direction] as MQNode) + if (brack === cursor.parent.parent && cursor[side as Direction]) { + // move the stuff between + new Fragment( + cursor[side as Direction] as MQNode, + cursor.parent.ends[side as Direction] as MQNode, + -side as Direction + ) // me and ghost outside + .disown() + .withDirAdopt( + -side as Direction, + brack.parent, + brack, + brack[side as Direction] as MQNode + ) .jQ.insDirOf(side as Direction, brack.jQ); } - brack.bubble(function (node) { node.reflow(); return undefined; }); - } - else { - brack = this, side = brack.side; - if (brack.replacedFragment) brack.side = 0; // wrapping seln, don't be one-sided - else if (cursor[-side as Direction]) { // elsewise, auto-expand so ghost is at far end - brack.replaces(new Fragment(cursor[-side as Direction] as MQNode, cursor.parent.ends[-side as Direction] as MQNode, side as Direction)); + brack.bubble(function (node) { + node.reflow(); + return undefined; + }); + } else { + (brack = this), (side = brack.side); + if (brack.replacedFragment) brack.side = 0; + // wrapping seln, don't be one-sided + else if (cursor[-side as Direction]) { + // elsewise, auto-expand so ghost is at far end + brack.replaces( + new Fragment( + cursor[-side as Direction] as MQNode, + cursor.parent.ends[-side as Direction] as MQNode, + side as Direction + ) + ); cursor[-side as Direction] = 0; } super.createLeftOf(cursor); } if (side === L) cursor.insAtLeftEnd(brack.ends[L] as MQNode); else cursor.insRightOf(brack); - }; - placeCursor () {}; - unwrap () { - (this.ends[L] as MQNode).children().disown().adopt(this.parent, this, this[R]) + } + placeCursor() {} + unwrap() { + (this.ends[L] as MQNode) + .children() + .disown() + .adopt(this.parent, this, this[R]) .jQ.insertAfter(this.jQ); this.remove(); - }; - deleteSide (side:Direction, outward:boolean, cursor:Cursor) { - var parent = this.parent, sib = this[side], farEnd = parent.ends[side]; + } + deleteSide(side: Direction, outward: boolean, cursor: Cursor) { + var parent = this.parent, + sib = this[side], + farEnd = parent.ends[side]; - if (side === this.side) { // deleting non-ghost of one-sided bracket, unwrap + if (side === this.side) { + // deleting non-ghost of one-sided bracket, unwrap this.unwrap(); - sib ? cursor.insDirOf(-side as Direction, sib) : cursor.insAtDirEnd(side, parent); + sib + ? cursor.insDirOf(-side as Direction, sib) + : cursor.insAtDirEnd(side, parent); return; } - var opts = cursor.options, wasSolid = !this.side; + var opts = cursor.options, + wasSolid = !this.side; this.side = -side as Direction; // if deleting like, outer close-brace of [(1+2)+3} where inner open-paren - if (this.matchBrack(opts, side, (this.ends[L] as MQNode).ends[this.side])) { // is ghost, - this.closeOpposing((this.ends[L] as MQNode).ends[this.side as Direction] as Bracket); // then become [1+2)+3 + if (this.matchBrack(opts, side, (this.ends[L] as MQNode).ends[this.side])) { + // is ghost, + this.closeOpposing( + (this.ends[L] as MQNode).ends[this.side as Direction] as Bracket + ); // then become [1+2)+3 var origEnd = (this.ends[L] as MQNode).ends[side]; this.unwrap(); if (origEnd) origEnd.siblingCreated(cursor.options, side); if (sib) { - cursor.insDirOf(-side as Direction, sib) + cursor.insDirOf(-side as Direction, sib); } else { cursor.insAtDirEnd(side, parent); } - } - else { // if deleting like, inner close-brace of ([1+2}+3) where outer + } else { + // if deleting like, inner close-brace of ([1+2}+3) where outer - if (this.matchBrack(opts, side, this.parent.parent)) { // open-paren is + if (this.matchBrack(opts, side, this.parent.parent)) { + // open-paren is (this.parent.parent as Bracket).closeOpposing(this); // ghost, then become [1+2+3) (this.parent.parent as Bracket).unwrap(); } // else if deleting outward from a solid pair, unwrap else if (outward && wasSolid) { this.unwrap(); - sib ? cursor.insDirOf(-side as Direction, sib) : cursor.insAtDirEnd(side, parent); + sib + ? cursor.insDirOf(-side as Direction, sib) + : cursor.insAtDirEnd(side, parent); return; - } - else { // else deleting just one of a pair of brackets, become one-sided + } else { + // else deleting just one of a pair of brackets, become one-sided this.sides[side] = getOppBracketSide(this); - var $brack = this.delimjQs.removeClass('mq-ghost') - .eq(side === L ? 0 : 1).addClass('mq-ghost'); + var $brack = this.delimjQs + .removeClass('mq-ghost') + .eq(side === L ? 0 : 1) + .addClass('mq-ghost'); this.replaceBracket($brack, side); } - if (sib) { // auto-expand so ghost is at far end + if (sib) { + // auto-expand so ghost is at far end var origEnd = (this.ends[L] as MQNode).ends[side]; - new Fragment(sib, farEnd as MQNode, -side as Direction).disown() - .withDirAdopt(-side as Direction, this.ends[L] as MQNode, origEnd as MQNode, 0) - .jQ.insAtDirEnd(side, (this.ends[L] as MQNode).jQ.removeClass('mq-empty')); + new Fragment(sib, farEnd as MQNode, -side as Direction) + .disown() + .withDirAdopt( + -side as Direction, + this.ends[L] as MQNode, + origEnd as MQNode, + 0 + ) + .jQ.insAtDirEnd( + side, + (this.ends[L] as MQNode).jQ.removeClass('mq-empty') + ); if (origEnd) origEnd.siblingCreated(cursor.options, side); cursor.insDirOf(-side as Direction, sib); } // didn't auto-expand, cursor goes just outside or just inside parens - else (outward ? cursor.insDirOf(side, this) - : cursor.insAtDirEnd(side, this.ends[L] as MQNode)); + else + outward + ? cursor.insDirOf(side, this) + : cursor.insAtDirEnd(side, this.ends[L] as MQNode); } - }; - replaceBracket ($brack:$, side:BracketSide) { + } + replaceBracket($brack: $, side: BracketSide) { var symbol = this.getSymbol(side); $brack.html(symbol.html).css('width', symbol.width); @@ -1043,33 +1278,37 @@ class Bracket extends DelimsNode { } else { $brack.prev().css('margin-right', symbol.width); } - }; - deleteTowards (dir:Direction, cursor:Cursor) { + } + deleteTowards(dir: Direction, cursor: Cursor) { this.deleteSide(-dir as Direction, false, cursor); - }; - finalizeTree () { - (this.ends[L] as MQNode).deleteOutOf = function(dir:Direction, cursor:Cursor) { + } + finalizeTree() { + (this.ends[L] as MQNode).deleteOutOf = function ( + dir: Direction, + cursor: Cursor + ) { (this.parent as Bracket).deleteSide(dir, true, cursor); }; // FIXME HACK: after initial creation/insertion, finalizeTree would only be // called if the paren is selected and replaced, e.g. by LiveFraction - this.finalizeTree = this.intentionalBlur = function() { + this.finalizeTree = this.intentionalBlur = function () { this.delimjQs.eq(this.side === L ? 1 : 0).removeClass('mq-ghost'); this.side = 0; }; - }; - siblingCreated (_opts:Options, dir:Direction) { // if something typed between ghost and far + } + siblingCreated(_opts: Options, dir: Direction) { + // if something typed between ghost and far if (dir === -this.side) this.finalizeTree(); // end of its block, solidify - }; -}; + } +} -function getOppBracketSide (bracket:Bracket) { +function getOppBracketSide(bracket: Bracket) { var side = bracket.side as Direction; var data = bracket.sides[side]; return { ch: OPP_BRACKS[data.ch as keyof typeof OPP_BRACKS], - ctrlSeq: OPP_BRACKS[data.ctrlSeq as keyof typeof OPP_BRACKS] - } + ctrlSeq: OPP_BRACKS[data.ctrlSeq as keyof typeof OPP_BRACKS], + }; } var OPP_BRACKS = { @@ -1086,139 +1325,181 @@ var OPP_BRACKS = { '\\langle ': '\\rangle ', '\\rangle ': '\\langle ', '|': '|', - '\\lVert ' : '\\rVert ', - '\\rVert ' : '\\lVert ', + '\\lVert ': '\\rVert ', + '\\rVert ': '\\lVert ', }; var BRACKET_NAMES = { '⟨': 'angle-bracket', '⟩': 'angle-bracket', - '|': 'pipe' + '|': 'pipe', }; -function bindCharBracketPair(open:keyof typeof OPP_BRACKS, ctrlSeq:string, name:string) { +function bindCharBracketPair( + open: keyof typeof OPP_BRACKS, + ctrlSeq: string, + name: string +) { var ctrlSeq = ctrlSeq || open; var close = OPP_BRACKS[open]; var end = OPP_BRACKS[ctrlSeq as keyof typeof OPP_BRACKS]; CharCmds[open] = () => new Bracket(L, open, close, ctrlSeq, end); CharCmds[close] = () => new Bracket(R, open, close, ctrlSeq, end); - BRACKET_NAMES[open as keyof typeof BRACKET_NAMES] = BRACKET_NAMES[close as keyof typeof BRACKET_NAMES] = name; + BRACKET_NAMES[open as keyof typeof BRACKET_NAMES] = BRACKET_NAMES[ + close as keyof typeof BRACKET_NAMES + ] = name; } bindCharBracketPair('(', '', 'parenthesis'); bindCharBracketPair('[', '', 'bracket'); bindCharBracketPair('{', '\\{', 'brace'); -LatexCmds.langle = () => new Bracket(L, '⟨', '⟩', '\\langle ', '\\rangle '); -LatexCmds.rangle = () => new Bracket(R, '⟨', '⟩', '\\langle ', '\\rangle '); +LatexCmds.langle = () => + new Bracket(L, '⟨', '⟩', '\\langle ', '\\rangle '); +LatexCmds.rangle = () => + new Bracket(R, '⟨', '⟩', '\\langle ', '\\rangle '); CharCmds['|'] = () => new Bracket(L, '|', '|', '|', '|'); -LatexCmds.lVert = () => new Bracket(L, '∥', '∥', '\\lVert ', '\\rVert '); -LatexCmds.rVert = () => new Bracket(R, '∥', '∥', '\\lVert ', '\\rVert '); - +LatexCmds.lVert = () => + new Bracket(L, '∥', '∥', '\\lVert ', '\\rVert '); +LatexCmds.rVert = () => + new Bracket(R, '∥', '∥', '\\lVert ', '\\rVert '); LatexCmds.left = class extends MathCommand { - parser () { + parser() { var regex = Parser.regex; var string = Parser.string; var optWhitespace = Parser.optWhitespace; - return optWhitespace.then(regex(/^(?:[([|]|\\\{|\\langle(?![a-zA-Z])|\\lVert(?![a-zA-Z]))/)) - .then(function(ctrlSeq) { + return optWhitespace + .then(regex(/^(?:[([|]|\\\{|\\langle(?![a-zA-Z])|\\lVert(?![a-zA-Z]))/)) + .then(function (ctrlSeq) { var open = ctrlSeq.replace(/^\\/, ''); - if (ctrlSeq=="\\langle") { open = '⟨'; ctrlSeq = ctrlSeq + ' '; } - if (ctrlSeq=="\\lVert") { open = '∥'; ctrlSeq = ctrlSeq + ' '; } + if (ctrlSeq == '\\langle') { + open = '⟨'; + ctrlSeq = ctrlSeq + ' '; + } + if (ctrlSeq == '\\lVert') { + open = '∥'; + ctrlSeq = ctrlSeq + ' '; + } return latexMathParser.then(function (block) { - return string('\\right').skip(optWhitespace) - .then(regex(/^(?:[\])|]|\\\}|\\rangle(?![a-zA-Z])|\\rVert(?![a-zA-Z]))/)).map(function(end) { + return string('\\right') + .skip(optWhitespace) + .then( + regex(/^(?:[\])|]|\\\}|\\rangle(?![a-zA-Z])|\\rVert(?![a-zA-Z]))/) + ) + .map(function (end) { var close = end.replace(/^\\/, ''); - if (end=="\\rangle") { close = '⟩'; end = end + ' '; } - if (end=="\\rVert") { close = '∥'; end = end + ' '; } + if (end == '\\rangle') { + close = '⟩'; + end = end + ' '; + } + if (end == '\\rVert') { + close = '∥'; + end = end + ' '; + } var cmd = new Bracket(0, open, close, ctrlSeq, end); - cmd.blocks = [ block ]; + cmd.blocks = [block]; block.adopt(cmd, 0, 0); return cmd; - }) - ; + }); }); - }) - ; - }; + }); + } }; LatexCmds.right = class extends MathCommand { - parser () { + parser() { return Parser.fail('unmatched \\right'); - }; + } }; var leftBinomialSymbol = SVG_SYMBOLS['(']; var rightBinomialSymbol = SVG_SYMBOLS[')']; class Binomial extends DelimsNode { - ctrlSeq = '\\binom'; htmlTemplate = - '' - + '' - + leftBinomialSymbol.html - + '' - + '' - + '' - + '&0' - + '&1' - + '' - + '' - + '' - + rightBinomialSymbol.html - + '' - + '' - ; - textTemplate = ['choose(',',',')']; + '' + + '' + + leftBinomialSymbol.html + + '' + + '' + + '' + + '&0' + + '&1' + + '' + + '' + + '' + + rightBinomialSymbol.html + + '' + + ''; + textTemplate = ['choose(', ',', ')']; mathspeakTemplate = ['StartBinomial,', 'Choose', ', EndBinomial']; ariaLabel = 'binomial'; -}; +} -LatexCmds.binom = -LatexCmds.binomial = Binomial; +LatexCmds.binom = LatexCmds.binomial = Binomial; LatexCmds.choose = class extends Binomial { - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { LiveFraction.prototype.createLeftOf(cursor); } }; class MathFieldNode extends MathCommand { - name:string; + name: string; ctrlSeq = '\\MathQuillMathField'; htmlTemplate = - '' - + '&0' - + '' - ; - parser () { + '' + + '&0' + + ''; + parser() { var self = this, - string = Parser.string, regex = Parser.regex, succeed = Parser.succeed; - return string('[').then(regex(/^[a-z][a-z0-9]*/i)).skip(string(']')) - .map(function(name) { self.name = name; }).or(succeed(undefined)) + string = Parser.string, + regex = Parser.regex, + succeed = Parser.succeed; + return string('[') + .then(regex(/^[a-z][a-z0-9]*/i)) + .skip(string(']')) + .map(function (name) { + self.name = name; + }) + .or(succeed(undefined)) .then(super.parser()); - }; - finalizeTree (options:CursorOptions) { - var ctrlr = new Controller(this.ends[L] as ControllerRoot, this.jQ, options); + } + finalizeTree(options: CursorOptions) { + var ctrlr = new Controller( + this.ends[L] as ControllerRoot, + this.jQ, + options + ); ctrlr.KIND_OF_MQ = 'MathField'; ctrlr.editable = true; ctrlr.createTextarea(); ctrlr.editablesTextareaEvents(); ctrlr.cursor.insAtRightEnd(ctrlr.root); RootBlockMixin(ctrlr.root); - }; - registerInnerField (innerFields:InnerFields, MathField:InnerMathField) { + } + registerInnerField(innerFields: InnerFields, MathField: InnerMathField) { const controller = (this.ends[L] as RootMathBlock).controller; const newField = new MathField(controller); - innerFields[this.name] = newField + innerFields[this.name] = newField; innerFields.push(newField); - }; - latex (){ return (this.ends[L] as MQNode).latex(); }; - text (){ return (this.ends[L] as MQNode).text(); }; -}; -LatexCmds.editable = // backcompat with before cfd3620 on #233 -LatexCmds.MathQuillMathField = MathFieldNode; + } + latex() { + return (this.ends[L] as MQNode).latex(); + } + text() { + return (this.ends[L] as MQNode).text(); + } +} +LatexCmds.editable = LatexCmds.MathQuillMathField = MathFieldNode; // backcompat with before cfd3620 on #233 // Embed arbitrary things // Probably the closest DOM analogue would be an iframe? @@ -1228,27 +1509,34 @@ LatexCmds.MathQuillMathField = MathFieldNode; // or by calling the global public API method .registerEmbed() // and rendering LaTeX like \embed{registeredName} (see test). class EmbedNode extends MQSymbol { - setOptions (options:EmbedOptions) { - function noop () { return ""; } + setOptions(options: EmbedOptions) { + function noop() { + return ''; + } this.text = options.text || noop; - this.htmlTemplate = options.htmlString || ""; + this.htmlTemplate = options.htmlString || ''; this.latex = options.latex || noop; return this; - }; - parser () { + } + parser() { var self = this, - string = Parser.string, regex = Parser.regex, succeed = Parser.succeed; - return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}')) - .then(function(name) { + string = Parser.string, + regex = Parser.regex, + succeed = Parser.succeed; + return string('{') + .then(regex(/^[a-z][a-z0-9]*/i)) + .skip(string('}')) + .then(function (name) { // the chars allowed in the optional data block are arbitrary other than // excluding curly braces and square brackets (which'd be too confusing) - return string('[').then(regex(/^[-\w\s]*/)).skip(string(']')) - .or(succeed(undefined)).map(function(data) { + return string('[') + .then(regex(/^[-\w\s]*/)) + .skip(string(']')) + .or(succeed(undefined)) + .map(function (data) { return self.setOptions(EMBEDS[name](data)); - }) - ; - }) - ; - }; -}; + }); + }); + } +} LatexCmds.embed = EmbedNode; diff --git a/src/commands/text.ts b/src/commands/text.ts index 2c348a1e9..625d8eb83 100644 --- a/src/commands/text.ts +++ b/src/commands/text.ts @@ -11,17 +11,16 @@ class TextBlock extends MQNode { ctrlSeq = '\\text'; ariaLabel = 'Text'; - replacedText?:string; - anticursorPosition?:number; + replacedText?: string; + anticursorPosition?: number; - replaces (replacedText:Fragment | string) { + replaces(replacedText: Fragment | string) { if (replacedText instanceof Fragment) this.replacedText = replacedText.remove().jQ.text(); - else if (typeof replacedText === 'string') - this.replacedText = replacedText; - }; + else if (typeof replacedText === 'string') this.replacedText = replacedText; + } - jQadd (jQ:$) { + jQadd(jQ: $) { super.jQadd(jQ); const endsL = this.ends[L]; if (endsL) { @@ -31,9 +30,9 @@ class TextBlock extends MQNode { } } return this.jQ; - }; + } - createLeftOf (cursor:Cursor) { + createLeftOf(cursor: Cursor) { var textBlock = this; super.createLeftOf(cursor); @@ -47,10 +46,13 @@ class TextBlock extends MQNode { if (textBlockR) textBlockR.siblingCreated(cursor.options, L); const textBlockL = textBlock[L]; if (textBlockL) textBlockL.siblingCreated(cursor.options, R); - textBlock.bubble(function (node) { node.reflow(); return undefined}); - }; + textBlock.bubble(function (node) { + node.reflow(); + return undefined; + }); + } - parser () { + parser() { var textBlock = this; // TODO: correctly parse text mode @@ -58,123 +60,149 @@ class TextBlock extends MQNode { var regex = Parser.regex; var optWhitespace = Parser.optWhitespace; return optWhitespace - .then(string('{')).then(regex(/^[^}]*/)).skip(string('}')) - .map(function(text) { + .then(string('{')) + .then(regex(/^[^}]*/)) + .skip(string('}')) + .map(function (text) { if (text.length === 0) return new Fragment(); new TextPiece(text).adopt(textBlock, 0, 0); return textBlock; - }) as ParserAny - ; - }; + }) as ParserAny; + } - textContents () { - return this.foldChildren('', function(text, child) { + textContents() { + return this.foldChildren('', function (text, child) { return text + (child as TextPiece).textStr; }); - }; - text () { return '"' + this.textContents() + '"'; }; - latex () { + } + text() { + return '"' + this.textContents() + '"'; + } + latex() { var contents = this.textContents(); if (contents.length === 0) return ''; - return this.ctrlSeq + '{' + contents.replace(/\\/g, '\\backslash ').replace(/[{}]/g, '\\$&') + '}'; - }; - html () { return ( - '' - + this.textContents() - + '' + this.ctrlSeq + + '{' + + contents.replace(/\\/g, '\\backslash ').replace(/[{}]/g, '\\$&') + + '}' ); - }; + } + html() { + return ( + '' + + this.textContents() + + '' + ); + } mathspeakTemplate = ['StartText', 'EndText']; - mathspeak (opts?:MathspeakOptions) { + mathspeak(opts?: MathspeakOptions) { if (opts && opts.ignoreShorthand) { - return this.mathspeakTemplate[0]+', '+this.textContents() +', '+this.mathspeakTemplate[1] + return ( + this.mathspeakTemplate[0] + + ', ' + + this.textContents() + + ', ' + + this.mathspeakTemplate[1] + ); } else { return this.textContents(); } - }; - isTextBlock () { + } + isTextBlock() { return true; - }; + } // editability methods: called by the cursor for editing, cursor movements, // and selection of the MathQuill tree, these all take in a direction and // the cursor - moveTowards (dir:Direction, cursor:Cursor) { + moveTowards(dir: Direction, cursor: Cursor) { cursor.insAtDirEnd(-dir as Direction, this); - cursor.controller.aria.queueDirEndOf(-dir as Direction).queue(cursor.parent, true); - }; - moveOutOf (dir:Direction, cursor:Cursor) { + cursor.controller.aria + .queueDirEndOf(-dir as Direction) + .queue(cursor.parent, true); + } + moveOutOf(dir: Direction, cursor: Cursor) { cursor.insDirOf(dir, this); cursor.controller.aria.queueDirOf(dir).queue(this); - }; - unselectInto (dir:Direction, cursor:Cursor) { + } + unselectInto(dir: Direction, cursor: Cursor) { this.moveTowards(dir, cursor); } // TODO: make these methods part of a shared mixin or something. - selectTowards (dir:Direction, cursor:Cursor) { + selectTowards(dir: Direction, cursor: Cursor) { MathCommand.prototype.selectTowards.call(this, dir, cursor); } - deleteTowards (dir:Direction, cursor:Cursor) { + deleteTowards(dir: Direction, cursor: Cursor) { MathCommand.prototype.deleteTowards.call(this, dir, cursor); } - selectOutOf (dir:Direction, cursor:Cursor) { + selectOutOf(dir: Direction, cursor: Cursor) { cursor.insDirOf(dir, this); - }; - deleteOutOf (_dir:Direction, cursor:Cursor) { + } + deleteOutOf(_dir: Direction, cursor: Cursor) { // backspace and delete at ends of block don't unwrap if (this.isEmpty()) cursor.insRightOf(this); - }; - write (cursor:Cursor, ch:string) { + } + write(cursor: Cursor, ch: string) { cursor.show().deleteSelection(); if (ch !== '$') { let cursorL = cursor[L]; if (!cursorL) new TextPiece(ch).createLeftOf(cursor); else if (cursorL instanceof TextPiece) cursorL.appendText(ch); - } - else if (this.isEmpty()) { + } else if (this.isEmpty()) { cursor.insRightOf(this); - new VanillaSymbol('\\$','$').createLeftOf(cursor); - } - else if (!cursor[R]) cursor.insRightOf(this); + new VanillaSymbol('\\$', '$').createLeftOf(cursor); + } else if (!cursor[R]) cursor.insRightOf(this); else if (!cursor[L]) cursor.insLeftOf(this); - else { // split apart + else { + // split apart var leftBlock = new TextBlock(); var leftPc = this.ends[L]; if (leftPc) { leftPc.disown().jQ.detach(); - leftPc.adopt(leftBlock, 0, 0); + leftPc.adopt(leftBlock, 0, 0); } cursor.insLeftOf(this); super.createLeftOf.call(leftBlock, cursor); // micro-optimization, not for correctness } - this.bubble(function (node) { node.reflow(); return undefined; }); + this.bubble(function (node) { + node.reflow(); + return undefined; + }); // TODO needs tests cursor.controller.aria.alert(ch); - }; - writeLatex (cursor:Cursor, latex:string) { + } + writeLatex(cursor: Cursor, latex: string) { const cursorL = cursor[L]; if (!cursorL) new TextPiece(latex).createLeftOf(cursor); else if (cursorL instanceof TextPiece) cursorL.appendText(latex); - this.bubble(function (node) { node.reflow(); return undefined }); - }; + this.bubble(function (node) { + node.reflow(); + return undefined; + }); + } - seek (pageX:number, cursor:Cursor) { + seek(pageX: number, cursor: Cursor) { cursor.hide(); var textPc = TextBlockFuseChildren(this); if (!textPc) return; // insert cursor at approx position in DOMTextNode - var avgChWidth = this.jQ.width()/this.text.length; - var approxPosition = Math.round((pageX - this.jQ.offset().left)/avgChWidth); + var avgChWidth = this.jQ.width() / this.text.length; + var approxPosition = Math.round( + (pageX - this.jQ.offset().left) / avgChWidth + ); if (approxPosition <= 0) cursor.insAtLeftEnd(this); - else if (approxPosition >= textPc.textStr.length) cursor.insAtRightEnd(this); + else if (approxPosition >= textPc.textStr.length) + cursor.insAtRightEnd(this); else cursor.insLeftOf(textPc.splitRight(approxPosition)); // move towards mousedown (pageX) @@ -187,52 +215,57 @@ class TextBlock extends MQNode { prevDispl = displ; displ = pageX - cursor.offset().left; } - if (dir*displ < -dir*prevDispl) (cursor[-dir as Direction] as MQNode).moveTowards(-dir as Direction, cursor); + if (dir * displ < -dir * prevDispl) + (cursor[-dir as Direction] as MQNode).moveTowards( + -dir as Direction, + cursor + ); if (!cursor.anticursor) { // about to start mouse-selecting, the anticursor is gonna get put here const cursorL = cursor[L]; - this.anticursorPosition = cursorL && (cursorL as TextPiece).textStr.length; + this.anticursorPosition = + cursorL && (cursorL as TextPiece).textStr.length; // ^ get it? 'cos if there's no cursor[L], it's 0... I'm a terrible person. - } - else if (cursor.anticursor.parent === this) { + } else if (cursor.anticursor.parent === this) { // mouse-selecting within this TextBlock, re-insert the anticursor const cursorL = cursor[L]; var cursorPosition = cursorL && (cursorL as TextPiece).textStr.length; if (this.anticursorPosition === cursorPosition) { cursor.anticursor = Anticursor.fromCursor(cursor); - } - else { + } else { if (this.anticursorPosition! < cursorPosition!) { - var newTextPc = (cursorL as any as TextPiece).splitRight(this.anticursorPosition!); + var newTextPc = (cursorL as any as TextPiece).splitRight( + this.anticursorPosition! + ); cursor[L] = newTextPc; - } - else { + } else { const cursorR = cursor[R] as any as TextPiece; - var newTextPc = cursorR.splitRight(this.anticursorPosition! - cursorPosition!); + var newTextPc = cursorR.splitRight( + this.anticursorPosition! - cursorPosition! + ); } cursor.anticursor = new Anticursor(this, newTextPc[L], newTextPc); } } - }; + } - blur (cursor:Cursor) { + blur(cursor: Cursor) { MathBlock.prototype.blur.call(this, cursor); if (!cursor) return; if (this.textContents() === '') { this.remove(); if (cursor[L] === this) cursor[L] = this[L]; else if (cursor[R] === this) cursor[R] = this[R]; - } - else TextBlockFuseChildren(this); - }; + } else TextBlockFuseChildren(this); + } - focus () { + focus() { MathBlock.prototype.focus.call(this); } -}; +} -function TextBlockFuseChildren(self:TextBlock) { +function TextBlockFuseChildren(self: TextBlock) { self.jQ[0].normalize(); var textPcDom = self.jQ[0].firstChild as Text; @@ -258,67 +291,74 @@ function TextBlockFuseChildren(self:TextBlock) { */ class TextPiece extends MQNode { textStr: string; - dom:Text; - - constructor (text:string) { + dom: Text; + + constructor(text: string) { super(); this.textStr = text; - }; - jQadd (dom:Text) { + } + jQadd(dom: Text) { this.dom = dom; this.jQ = $(dom); return this.jQ; - }; - jQize () { + } + jQize() { return this.jQadd(document.createTextNode(this.textStr)); - }; - appendText (text:string) { + } + appendText(text: string) { this.textStr += text; this.dom.appendData(text); - }; - prependText (text:string) { + } + prependText(text: string) { this.textStr = text + this.textStr; this.dom.insertData(0, text); - }; - insTextAtDirEnd (text:string, dir:Direction) { + } + insTextAtDirEnd(text: string, dir: Direction) { prayDirection(dir); if (dir === R) this.appendText(text); else this.prependText(text); - }; - splitRight (i:number) { - var newPc = new TextPiece(this.textStr.slice(i)).adopt(this.parent, this, this[R]); + } + splitRight(i: number) { + var newPc = new TextPiece(this.textStr.slice(i)).adopt( + this.parent, + this, + this[R] + ); newPc.jQadd(this.dom.splitText(i)); this.textStr = this.textStr.slice(0, i); return newPc; - }; + } - endChar(dir:Direction, text:string) { + endChar(dir: Direction, text: string) { return text.charAt(dir === L ? 0 : -1 + text.length); } - moveTowards (dir:Direction, cursor:Cursor) { + moveTowards(dir: Direction, cursor: Cursor) { prayDirection(dir); - var ch = this.endChar(-dir as Direction, this.textStr) + var ch = this.endChar(-dir as Direction, this.textStr); var from = this[-dir as Direction]; if (from instanceof TextPiece) from.insTextAtDirEnd(ch, dir); else new TextPiece(ch).createDir(-dir as Direction, cursor); return this.deleteTowards(dir, cursor); - }; + } - mathspeak () { return this.textStr; }; - latex () { return this.textStr; }; + mathspeak() { + return this.textStr; + } + latex() { + return this.textStr; + } - deleteTowards (dir:Direction, cursor:Cursor) { + deleteTowards(dir: Direction, cursor: Cursor) { if (this.textStr.length > 1) { var deletedChar; if (dir === R) { this.dom.deleteData(0, 1); deletedChar = this.textStr[0]; this.textStr = this.textStr.slice(1); - } - else { + } else { // note that the order of these 2 lines is annoyingly important // (the second line mutates this.textStr.length) this.dom.deleteData(-1 + this.textStr.length, 1); @@ -326,28 +366,26 @@ class TextPiece extends MQNode { this.textStr = this.textStr.slice(0, -1); } cursor.controller.aria.queue(deletedChar); - } - else { + } else { this.remove(); this.jQ.remove(); cursor[dir] = this[dir]; cursor.controller.aria.queue(this.textStr); } - }; + } - selectTowards (dir:Direction, cursor:Cursor) { + selectTowards(dir: Direction, cursor: Cursor) { prayDirection(dir); var anticursor = cursor.anticursor; if (!anticursor) return; - var ch = this.endChar(-dir as Direction, this.textStr) + var ch = this.endChar(-dir as Direction, this.textStr); if (anticursor[dir] === this) { var newPc = new TextPiece(ch).createDir(dir, cursor); anticursor[dir] = newPc; cursor.insDirOf(dir, newPc); - } - else { + } else { var from = this[-dir as Direction]; if (from instanceof TextPiece) from.insTextAtDirEnd(ch, dir); else { @@ -364,107 +402,145 @@ class TextPiece extends MQNode { } return this.deleteTowards(dir, cursor); - }; -}; + } +} LatexCmds.text = -LatexCmds.textnormal = -LatexCmds.textrm = -LatexCmds.textup = -LatexCmds.textmd = TextBlock; - -function makeTextBlock(latex:string, ariaLabel:string, tagName:string, attrs:string) { + LatexCmds.textnormal = + LatexCmds.textrm = + LatexCmds.textup = + LatexCmds.textmd = + TextBlock; + +function makeTextBlock( + latex: string, + ariaLabel: string, + tagName: string, + attrs: string +) { return class extends TextBlock { ctrlSeq = latex; - mathspeakTemplate = ['Start'+ariaLabel, 'End'+ariaLabel]; + mathspeakTemplate = ['Start' + ariaLabel, 'End' + ariaLabel]; ariaLabel = ariaLabel; - html () { + html() { var cmdId = 'mathquill-command-id=' + this.id; - return '<'+tagName+' '+attrs+' '+cmdId+'>'+this.textContents()+''; + return ( + '<' + + tagName + + ' ' + + attrs + + ' ' + + cmdId + + '>' + + this.textContents() + + '' + ); } }; } -LatexCmds.em = LatexCmds.italic = LatexCmds.italics = -LatexCmds.emph = LatexCmds.textit = LatexCmds.textsl = - makeTextBlock('\\textit', 'Italic', 'i', 'class="mq-text-mode"'); -LatexCmds.strong = LatexCmds.bold = LatexCmds.textbf = - makeTextBlock('\\textbf', 'Bold', 'b', 'class="mq-text-mode"'); -LatexCmds.sf = LatexCmds.textsf = - makeTextBlock('\\textsf', 'Sans serif font', 'span', 'class="mq-sans-serif mq-text-mode"'); -LatexCmds.tt = LatexCmds.texttt = - makeTextBlock('\\texttt', 'Mono space font', 'span', 'class="mq-monospace mq-text-mode"'); -LatexCmds.textsc = - makeTextBlock('\\textsc', 'Variable font', 'span', 'style="font-variant:small-caps" class="mq-text-mode"'); -LatexCmds.uppercase = - makeTextBlock('\\uppercase', 'Uppercase', 'span', 'style="text-transform:uppercase" class="mq-text-mode"'); -LatexCmds.lowercase = - makeTextBlock('\\lowercase', 'Lowercase', 'span', 'style="text-transform:lowercase" class="mq-text-mode"'); - +LatexCmds.em = + LatexCmds.italic = + LatexCmds.italics = + LatexCmds.emph = + LatexCmds.textit = + LatexCmds.textsl = + makeTextBlock('\\textit', 'Italic', 'i', 'class="mq-text-mode"'); +LatexCmds.strong = + LatexCmds.bold = + LatexCmds.textbf = + makeTextBlock('\\textbf', 'Bold', 'b', 'class="mq-text-mode"'); +LatexCmds.sf = LatexCmds.textsf = makeTextBlock( + '\\textsf', + 'Sans serif font', + 'span', + 'class="mq-sans-serif mq-text-mode"' +); +LatexCmds.tt = LatexCmds.texttt = makeTextBlock( + '\\texttt', + 'Mono space font', + 'span', + 'class="mq-monospace mq-text-mode"' +); +LatexCmds.textsc = makeTextBlock( + '\\textsc', + 'Variable font', + 'span', + 'style="font-variant:small-caps" class="mq-text-mode"' +); +LatexCmds.uppercase = makeTextBlock( + '\\uppercase', + 'Uppercase', + 'span', + 'style="text-transform:uppercase" class="mq-text-mode"' +); +LatexCmds.lowercase = makeTextBlock( + '\\lowercase', + 'Lowercase', + 'span', + 'style="text-transform:lowercase" class="mq-text-mode"' +); class RootMathCommand extends MathCommand { - cursor:Cursor; - constructor (cursor:Cursor) { + cursor: Cursor; + constructor(cursor: Cursor) { super('$'); this.cursor = cursor; - }; + } htmlTemplate = '&0'; - createBlocks () { - super.createBlocks() + createBlocks() { + super.createBlocks(); const endsL = this.ends[L] as RootMathCommand; // TODO - how do we know this is a RootMathCommand? endsL.cursor = this.cursor; - endsL.write = function(cursor:Cursor, ch:string) { - if (ch !== '$') - MathBlock.prototype.write.call(this, cursor, ch); + endsL.write = function (cursor: Cursor, ch: string) { + if (ch !== '$') MathBlock.prototype.write.call(this, cursor, ch); else if (this.isEmpty()) { cursor.insRightOf(this.parent); this.parent.deleteTowards(undefined!, cursor); - new VanillaSymbol('\\$','$').createLeftOf(cursor.show()); - } - else if (!cursor[R]) - cursor.insRightOf(this.parent); - else if (!cursor[L]) - cursor.insLeftOf(this.parent); - else - MathBlock.prototype.write.call(this, cursor, ch); + new VanillaSymbol('\\$', '$').createLeftOf(cursor.show()); + } else if (!cursor[R]) cursor.insRightOf(this.parent); + else if (!cursor[L]) cursor.insLeftOf(this.parent); + else MathBlock.prototype.write.call(this, cursor, ch); }; - }; - latex () { + } + latex() { return '$' + (this.ends[L] as MQNode).latex() + '$'; - }; -}; + } +} class RootTextBlock extends RootMathBlock { - keystroke (key:string, e:KeyboardEvent, ctrlr:Controller) { + keystroke(key: string, e: KeyboardEvent, ctrlr: Controller) { if (key === 'Spacebar' || key === 'Shift-Spacebar') return; return super.keystroke(key, e, ctrlr); - }; - write (cursor:Cursor, ch:string) { + } + write(cursor: Cursor, ch: string) { cursor.show().deleteSelection(); - if (ch === '$') - new RootMathCommand(cursor).createLeftOf(cursor); + if (ch === '$') new RootMathCommand(cursor).createLeftOf(cursor); else { var html; if (ch === '<') html = '<'; else if (ch === '>') html = '>'; new VanillaSymbol(ch, html).createLeftOf(cursor); } - }; -}; -API.TextField = function(APIClasses:APIClasses) { + } +} +API.TextField = function (APIClasses: APIClasses) { return class extends APIClasses.EditableField { static RootBlock = RootTextBlock; - __mathquillify () { + __mathquillify() { return super.__mathquillify('mq-editable-field mq-text-mode'); - }; - latex (latex:string) { + } + latex(latex: string) { if (arguments.length > 0) { this.__controller.renderLatexText(latex); - if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur(); + if (this.__controller.blurred) + this.__controller.cursor.hide().parent.blur(); return this; } return this.__controller.exportLatex(); - }; + } }; }; diff --git a/src/controller.ts b/src/controller.ts index 5d3815650..53f6d44dc 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -2,63 +2,69 @@ * Controller for a MathQuill instance ********************************************/ class ControllerBase { - id:number; - data:ControllerData; - root:ControllerRoot; - container:$; - options:CursorOptions; - aria:Aria; - ariaLabel:string; - ariaPostLabel:string; - cursor:Cursor; - editable:boolean | undefined; - _ariaAlertTimeout:number; - KIND_OF_MQ:KIND_OF_MQ; - textarea:$ | undefined; - textareaSpan:$ | undefined; - mathspeakSpan:$ | undefined; + id: number; + data: ControllerData; + root: ControllerRoot; + container: $; + options: CursorOptions; + aria: Aria; + ariaLabel: string; + ariaPostLabel: string; + cursor: Cursor; + editable: boolean | undefined; + _ariaAlertTimeout: number; + KIND_OF_MQ: KIND_OF_MQ; + textarea: $ | undefined; + textareaSpan: $ | undefined; + mathspeakSpan: $ | undefined; - constructor (root:ControllerRoot, container:$, options:CursorOptions) { + constructor(root: ControllerRoot, container: $, options: CursorOptions) { this.id = root.id; this.data = {}; this.root = root; this.container = container; this.options = options; - + this.aria = new Aria(this.getControllerSelf()); this.ariaLabel = 'Math Input'; this.ariaPostLabel = ''; root.controller = this.getControllerSelf(); - this.cursor = root.cursor = new Cursor(root, options, this.getControllerSelf()); + this.cursor = root.cursor = new Cursor( + root, + options, + this.getControllerSelf() + ); // TODO: stop depending on root.cursor, and rm it - }; + } getControllerSelf() { // dance we have to do to tell this thing it's a full controller return this as any as Controller; } - handle (name:HandlerName, dir?:Direction) { + handle(name: HandlerName, dir?: Direction) { var handlers = this.options.handlers; if (handlers && handlers.fns[name]) { var mq = new handlers.APIClasses[this.KIND_OF_MQ](this); if (dir === L || dir === R) handlers.fns[name](dir, mq); else handlers.fns[name](mq); } - }; + } - static notifyees:((cursor:Cursor, e:ControllerEvent) => void)[] = []; - static onNotify (f:(cursor:Cursor, e:ControllerEvent) => void) { ControllerBase.notifyees.push(f); }; - notify (e:ControllerEvent) { + static notifyees: ((cursor: Cursor, e: ControllerEvent) => void)[] = []; + static onNotify(f: (cursor: Cursor, e: ControllerEvent) => void) { + ControllerBase.notifyees.push(f); + } + notify(e: ControllerEvent) { for (var i = 0; i < ControllerBase.notifyees.length; i += 1) { ControllerBase.notifyees[i](this.cursor, e); } return this; - }; - setAriaLabel (ariaLabel:string) { + } + setAriaLabel(ariaLabel: string) { var oldAriaLabel = this.getAriaLabel(); if (ariaLabel && typeof ariaLabel === 'string' && ariaLabel !== '') { this.ariaLabel = ariaLabel; @@ -75,8 +81,8 @@ class ControllerBase { this.updateMathspeak(); } return this; - }; - getAriaLabel () { + } + getAriaLabel() { if (this.ariaLabel !== 'Math Input') { return this.ariaLabel; } else if (this.editable) { @@ -84,19 +90,22 @@ class ControllerBase { } else { return ''; } - }; - setAriaPostLabel (ariaPostLabel:string, timeout:number) { - if(ariaPostLabel && typeof ariaPostLabel === 'string' && ariaPostLabel !== '') { - if ( - ariaPostLabel !== this.ariaPostLabel && - typeof timeout === 'number' - ) { + } + setAriaPostLabel(ariaPostLabel: string, timeout: number) { + if ( + ariaPostLabel && + typeof ariaPostLabel === 'string' && + ariaPostLabel !== '' + ) { + if (ariaPostLabel !== this.ariaPostLabel && typeof timeout === 'number') { if (this._ariaAlertTimeout) clearTimeout(this._ariaAlertTimeout); this._ariaAlertTimeout = setTimeout(() => { if (this.containerHasFocus()) { // Voice the new label, but do not update content mathspeak to prevent double-speech. - this.aria.alert(this.root.mathspeak().trim() + ' ' + ariaPostLabel.trim()); - } else { + this.aria.alert( + this.root.mathspeak().trim() + ' ' + ariaPostLabel.trim() + ); + } else { // This mathquill does not have focus, so update its mathspeak. this.updateMathspeak(); } @@ -108,26 +117,26 @@ class ControllerBase { this.ariaPostLabel = ''; } return this; - }; - getAriaPostLabel () { + } + getAriaPostLabel() { return this.ariaPostLabel || ''; - }; - containerHasFocus () { + } + containerHasFocus() { return ( document.activeElement && this.container && this.container[0] && this.container[0].contains(document.activeElement) ); - }; + } - getTextareaOrThrow () { + getTextareaOrThrow() { var textarea = this.textarea; if (!textarea) throw new Error('expected a textarea'); return textarea; } - getTextareaSpanOrThrow () { + getTextareaSpanOrThrow() { var textareaSpan = this.textareaSpan; if (!textareaSpan) throw new Error('expected a textareaSpan'); return textareaSpan; @@ -135,11 +144,13 @@ class ControllerBase { // based on http://www.gh-mathspeak.com/examples/quick-tutorial/ // and http://www.gh-mathspeak.com/examples/grammar-rules/ - exportMathSpeak () { return this.root.mathspeak(); }; + exportMathSpeak() { + return this.root.mathspeak(); + } - // overridden - updateMathspeak () {}; - scrollHoriz () {}; - selectionChanged () {}; - setOverflowClasses () {}; -}; \ No newline at end of file + // overridden + updateMathspeak() {} + scrollHoriz() {} + selectionChanged() {} + setOverflowClasses() {} +} diff --git a/src/cursor.ts b/src/cursor.ts index 26e7349ac..323f93e2a 100644 --- a/src/cursor.ts +++ b/src/cursor.ts @@ -11,70 +11,79 @@ JS environment could actually contain many instances. */ //A fake cursor in the fake textbox that the math is rendered in. class Anticursor extends Point { - ancestors:Record = {}; - constructor (parent: MQNode, leftward?:NodeRef, rightward?:NodeRef) { - super(parent, leftward, rightward) + ancestors: Record = {}; + constructor(parent: MQNode, leftward?: NodeRef, rightward?: NodeRef) { + super(parent, leftward, rightward); } - static fromCursor (cursor:Cursor) { + static fromCursor(cursor: Cursor) { return new Anticursor(cursor.parent, cursor[L], cursor[R]); } } class Cursor extends Point { - controller:Controller - parent:MQNode; - options:CursorOptions; + controller: Controller; + parent: MQNode; + options: CursorOptions; /** Slightly more than just a "cache", this remembers the cursor's position in each block node, so that we can return to the right * point in that node when moving up and down among blocks. */ - upDownCache:Record = {}; + upDownCache: Record = {}; blink: () => void; - _jQ:$; - jQ:$; - selection:MQSelection | undefined; - intervalId:number; - anticursor:Anticursor | undefined; + _jQ: $; + jQ: $; + selection: MQSelection | undefined; + intervalId: number; + anticursor: Anticursor | undefined; - constructor (initParent:MQNode, options:CursorOptions, controller:Controller) { + constructor( + initParent: MQNode, + options: CursorOptions, + controller: Controller + ) { super(initParent); this.controller = controller; this.options = options; - var jQ = this.jQ = this._jQ = $(''); + var jQ = (this.jQ = this._jQ = $('')); //closured for setInterval - this.blink = function(){ jQ.toggleClass('mq-blink'); }; - }; + this.blink = function () { + jQ.toggleClass('mq-blink'); + }; + } - show () { + show() { this.jQ = this._jQ.removeClass('mq-blink'); - if (this.intervalId) //already was shown, just restart interval + if (this.intervalId) + //already was shown, just restart interval clearInterval(this.intervalId); - else { //was hidden and detached, insert this.jQ back into HTML DOM + else { + //was hidden and detached, insert this.jQ back into HTML DOM if (this[R]) { var selection = this.selection; - if ( selection && (selection.ends[L] as MQNode)[L] === this[L]) + if (selection && (selection.ends[L] as MQNode)[L] === this[L]) this.jQ.insertBefore(selection.jQ); - else - this.jQ.insertBefore((this[R] as MQNode).jQ.first()); - } - else - this.jQ.appendTo(this.parent.jQ); + else this.jQ.insertBefore((this[R] as MQNode).jQ.first()); + } else this.jQ.appendTo(this.parent.jQ); this.parent.focus(); } this.intervalId = setInterval(this.blink, 500); return this; - }; - hide () { - if (this.intervalId) - clearInterval(this.intervalId); + } + hide() { + if (this.intervalId) clearInterval(this.intervalId); this.intervalId = 0; this.jQ.detach(); this.jQ = $(); return this; - }; + } - withDirInsertAt (dir:Direction, parent:MQNode, withDir:NodeRef, oppDir:NodeRef) { + withDirInsertAt( + dir: Direction, + parent: MQNode, + withDir: NodeRef, + oppDir: NodeRef + ) { var oldParent = this.parent; this.parent = parent; this[dir as Direction] = withDir; @@ -83,28 +92,36 @@ class Cursor extends Point { // and the cursor has actually been moved // FIXME pass cursor to .blur() so text can fix cursor pointers when removing itself if (oldParent !== parent && oldParent.blur) oldParent.blur(this); - }; + } /** Place the cursor before or after `el`, according the side specified by `dir`. */ - insDirOf (dir:Direction, el:MQNode) { + insDirOf(dir: Direction, el: MQNode) { prayDirection(dir); this.jQ.insDirOf(dir, el.jQ); this.withDirInsertAt(dir, el.parent, el[dir], el); this.parent.jQ.addClass('mq-hasCursor'); return this; - }; - insLeftOf (el:MQNode) { return this.insDirOf(L, el); }; - insRightOf (el:MQNode) { return this.insDirOf(R, el); }; + } + insLeftOf(el: MQNode) { + return this.insDirOf(L, el); + } + insRightOf(el: MQNode) { + return this.insDirOf(R, el); + } /** Place the cursor inside `el` at either the left or right end, according the side specified by `dir`. */ - insAtDirEnd (dir:Direction, el:MQNode) { + insAtDirEnd(dir: Direction, el: MQNode) { prayDirection(dir); this.jQ.insAtDirEnd(dir, el.jQ); this.withDirInsertAt(dir, el, 0, el.ends[dir]); el.focus(); return this; - }; - insAtLeftEnd (el:MQNode) { return this.insAtDirEnd(L, el); }; - insAtRightEnd (el:MQNode) { return this.insAtDirEnd(R, el); }; + } + insAtLeftEnd(el: MQNode) { + return this.insAtDirEnd(L, el); + } + insAtRightEnd(el: MQNode) { + return this.insAtDirEnd(R, el); + } /** * jump up or down from one block Node to another: @@ -114,25 +131,24 @@ class Cursor extends Point { * + if not seek a position in the node that is horizontally closest to * the cursor's current position */ - jumpUpDown (from:MQNode, to:MQNode) { + jumpUpDown(from: MQNode, to: MQNode) { var self = this; self.upDownCache[from.id] = Point.copy(self); var cached = self.upDownCache[to.id]; if (cached) { var cachedR = cached[R]; if (cachedR) { - self.insLeftOf(cachedR) + self.insLeftOf(cachedR); } else { self.insAtRightEnd(cached.parent); } - } - else { + } else { var pageX = self.offset().left; to.seek(pageX, self); } self.controller.aria.queue(to, true); - }; - offset () { + } + offset() { //in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset() //returns all 0's on inline elements with negative margin-right (like //the cursor) at the end of their parent, so temporarily remove the @@ -140,36 +156,37 @@ class Cursor extends Point { //Opera bug DSK-360043 //http://bugs.jquery.com/ticket/11523 //https://github.com/jquery/jquery/pull/717 - var self = this, offset = self.jQ.removeClass('mq-cursor').offset(); + var self = this, + offset = self.jQ.removeClass('mq-cursor').offset(); self.jQ.addClass('mq-cursor'); return offset; } - unwrapGramp () { + unwrapGramp() { var gramp = this.parent.parent; var greatgramp = gramp.parent; var rightward = gramp[R]; var cursor = this; var leftward = gramp[L]; - gramp.disown().eachChild(function(uncle) { + gramp.disown().eachChild(function (uncle) { if (uncle.isEmpty()) return true; - uncle.children() + uncle + .children() .adopt(greatgramp, leftward, rightward) - .each(function(cousin) { + .each(function (cousin) { cousin.jQ.insertBefore(gramp.jQ.first()); return true; - }) - ; + }); leftward = uncle.ends[R]; return true; }); - if (!this[R]) { //then find something to be rightward to insLeftOf + if (!this[R]) { + //then find something to be rightward to insLeftOf var thisL = this[L]; - if (thisL) - this[R] = thisL[R]; + if (thisL) this[R] = thisL[R]; else { while (!this[R]) { var newParent = this.parent[R]; @@ -186,10 +203,8 @@ class Cursor extends Point { } var thisR = this[R]; - if (thisR) - this.insLeftOf(thisR); - else - this.insAtRightEnd(greatgramp); + if (thisR) this.insLeftOf(thisR); + else this.insAtRightEnd(greatgramp); gramp.jQ.remove(); @@ -197,26 +212,35 @@ class Cursor extends Point { var grampR = gramp[R]; if (grampL) grampL.siblingDeleted(cursor.options, R); if (grampR) grampR.siblingDeleted(cursor.options, L); - }; - startSelection () { - var anticursor = this.anticursor = Anticursor.fromCursor(this); + } + startSelection() { + var anticursor = (this.anticursor = Anticursor.fromCursor(this)); var ancestors = anticursor.ancestors; - for (var ancestor:MQNode | Anticursor= anticursor; ancestor.parent; ancestor = ancestor.parent) { + for ( + var ancestor: MQNode | Anticursor = anticursor; + ancestor.parent; + ancestor = ancestor.parent + ) { ancestors[ancestor.parent.id] = ancestor; } - }; - endSelection () { + } + endSelection() { delete this.anticursor; - }; - select () { + } + select() { var _lca; var anticursor = this.anticursor!; - if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false; + if (this[L] === anticursor[L] && this.parent === anticursor.parent) + return false; // Find the lowest common ancestor (`lca`), and the ancestor of the cursor // whose parent is the LCA (which'll be an end of the selection fragment). - for (var ancestor:MQNode | Point | undefined = this; ancestor.parent; ancestor = ancestor.parent) { + for ( + var ancestor: MQNode | Point | undefined = this; + ancestor.parent; + ancestor = ancestor.parent + ) { if (ancestor.parent.id in anticursor.ancestors) { _lca = ancestor.parent; break; @@ -239,7 +263,9 @@ class Cursor extends Point { // parent and guaranteed that if both are Points, they are not the same, // and we have to figure out which is the left end and which the right end // of the selection. - var leftEnd, rightEnd, dir:Direction = R; + var leftEnd, + rightEnd, + dir: Direction = R; // This is an extremely subtle algorithm. // As a special case, `ancestor` could be a Point and `antiAncestor` a Node @@ -252,7 +278,11 @@ class Cursor extends Point { // `ancestor` or to its right, if and only if `antiAncestor` is to // the right of `ancestor`. if (ancestor[L] !== antiAncestor) { - for (var rightward:NodeRef | Point | undefined = ancestor; rightward; rightward = rightward[R]) { + for ( + var rightward: NodeRef | Point | undefined = ancestor; + rightward; + rightward = rightward[R] + ) { if (rightward[R] === antiAncestor[R]) { dir = L; leftEnd = ancestor; @@ -270,29 +300,32 @@ class Cursor extends Point { if (leftEnd instanceof Point) leftEnd = leftEnd[R]; if (rightEnd instanceof Point) rightEnd = rightEnd[L]; - this.hide().selection = lca.selectChildren(leftEnd as MQNode, rightEnd as MQNode); + this.hide().selection = lca.selectChildren( + leftEnd as MQNode, + rightEnd as MQNode + ); var insEl = this.selection!.ends[dir] as MQNode; this.insDirOf(dir, insEl); this.selectionChanged(); return true; - }; - resetToEnd (controller:ControllerBase) { + } + resetToEnd(controller: ControllerBase) { this.clearSelection(); var root = controller.root; this[R] = 0; this[L] = root.ends[R]; this.parent = root; - }; - clearSelection () { + } + clearSelection() { if (this.selection) { this.selection.clear(); delete this.selection; this.selectionChanged(); } return this; - }; - deleteSelection () { + } + deleteSelection() { var selection = this.selection; if (!selection) return; @@ -301,8 +334,8 @@ class Cursor extends Point { selection.remove(); this.selectionChanged(); delete this.selection; - }; - replaceSelection () { + } + replaceSelection() { var seln = this.selection; if (seln) { this[L] = (seln.ends[L] as MQNode)[L]; @@ -310,47 +343,46 @@ class Cursor extends Point { delete this.selection; } return seln; - }; - depth () { - var node:MQNode | Point = this; + } + depth() { + var node: MQNode | Point = this; var depth = 0; - while (node = node.parent) { - depth += (node instanceof MathBlock) ? 1 : 0; + while ((node = node.parent)) { + depth += node instanceof MathBlock ? 1 : 0; } return depth; - }; - isTooDeep (offset?:number) { + } + isTooDeep(offset?: number) { if (this.options.maxDepth !== undefined) { return this.depth() + (offset || 0) > this.options.maxDepth; } else { return false; } - }; - + } // can be overridden - selectionChanged () {} + selectionChanged() {} } class MQSelection extends Fragment { - constructor (withDir:MQNode, oppDir:MQNode, dir?:Direction) { + constructor(withDir: MQNode, oppDir: MQNode, dir?: Direction) { super(withDir, oppDir, dir); this.jQ = this.jQ.wrapAll('').parent(); - //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it - }; - adopt (parent:MQNode, leftward:NodeRef, rightward:NodeRef) { - this.jQ.replaceWith(this.jQ = this.jQ.children()); + //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it + } + adopt(parent: MQNode, leftward: NodeRef, rightward: NodeRef) { + this.jQ.replaceWith((this.jQ = this.jQ.children())); return super.adopt(parent, leftward, rightward); - }; - clear () { + } + clear() { // using the browser's native .childNodes property so that we // don't discard text nodes. this.jQ.replaceWith(this.jQ[0].childNodes); return this; - }; - join (methodName:JoinMethod, separator:string = ''):string { - return this.fold('', function(fold, child) { + } + join(methodName: JoinMethod, separator: string = ''): string { + return this.fold('', function (fold, child) { return fold + separator + child[methodName](); }); - }; -}; + } +} diff --git a/src/fonts/Symbola-basic.css b/src/fonts/Symbola-basic.css index 3a8186b98..04b55490b 100644 --- a/src/fonts/Symbola-basic.css +++ b/src/fonts/Symbola-basic.css @@ -1,4 +1,5 @@ @font-face { font-family: Symbola; - src: url(data:application/font-woff;base64,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) format('woff'); -} \ No newline at end of file + src: url(data:application/font-woff;base64,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) + format('woff'); +} diff --git a/src/publicapi.ts b/src/publicapi.ts index 9349eee04..5c87b9fa5 100644 --- a/src/publicapi.ts +++ b/src/publicapi.ts @@ -2,36 +2,38 @@ * The publicly exposed MathQuill API. ********************************************************/ -var API:API = { +var API: API = {}; -}; - -var EMBEDS:Record EmbedOptions> = {}; +var EMBEDS: Record EmbedOptions> = {}; class OptionProcessors { - maxDepth: (n:number) => CursorOptions['maxDepth']; - leftRightIntoCmdGoes: (s:'up'|'down') => CursorOptions['leftRightIntoCmdGoes']; - autoCommands: (list:string) => CursorOptions['autoOperatorNames']; - autoOperatorNames: (list:string) => CursorOptions['autoOperatorNames']; - autoParenthesizedFunctions: (list:string) => CursorOptions['autoOperatorNames']; - quietEmptyDelimiters: (list:string) => CursorOptions['quietEmptyDelimiters']; + maxDepth: (n: number) => CursorOptions['maxDepth']; + leftRightIntoCmdGoes: ( + s: 'up' | 'down' + ) => CursorOptions['leftRightIntoCmdGoes']; + autoCommands: (list: string) => CursorOptions['autoOperatorNames']; + autoOperatorNames: (list: string) => CursorOptions['autoOperatorNames']; + autoParenthesizedFunctions: ( + list: string + ) => CursorOptions['autoOperatorNames']; + quietEmptyDelimiters: (list: string) => CursorOptions['quietEmptyDelimiters']; } const optionProcessors = new OptionProcessors(); type AutoDict = { _maxLength?: number; - [id:string]:any; -} + [id: string]: any; +}; class Options { - ignoreNextMousedown: (_el:MouseEvent) => boolean; + ignoreNextMousedown: (_el: MouseEvent) => boolean; substituteTextarea: () => HTMLElement; - substituteKeyboardEvents:typeof saneKeyboardEvents; + substituteKeyboardEvents: typeof saneKeyboardEvents; - restrictMismatchedBrackets?:boolean; - typingSlashCreatesNewFraction?:boolean; - charsThatBreakOutOfSupSub:string; - sumStartsWithNEquals?:boolean; + restrictMismatchedBrackets?: boolean; + typingSlashCreatesNewFraction?: boolean; + charsThatBreakOutOfSupSub: string; + sumStartsWithNEquals?: boolean; autoSubscriptNumerals?: boolean; supSubsRequireOperand?: boolean; spaceBehavesLikeTab?: boolean; @@ -47,15 +49,15 @@ class Options { statelessClipboard?: boolean; onPaste?: () => void; onCut?: () => void; - overrideTypedText?:(text:string) => void; - overrideKeystroke: (key:string, event:KeyboardEvent) => void; + overrideTypedText?: (text: string) => void; + overrideKeystroke: (key: string, event: KeyboardEvent) => void; autoOperatorNames: AutoDict; autoCommands: AutoDict; autoParenthesizedFunctions: AutoDict; - quietEmptyDelimiters: { [id:string]:any; }; + quietEmptyDelimiters: { [id: string]: any }; disableAutoSubstitutionInSubscripts?: boolean; - handlers: HandlerOptions -}; + handlers: HandlerOptions; +} class Progenote {} /** @@ -66,51 +68,61 @@ class Progenote {} * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can * be accessed. */ -var insistOnInterVer = function() { - if (window.console) console.warn( - 'You are using the MathQuill API without specifying an interface version, ' + - 'which will fail in v1.0.0. Easiest fix is to do the following before ' + - 'doing anything else:\n' + - '\n' + - ' MathQuill = MathQuill.getInterface(1);\n' + - ' // now MathQuill.MathField() works like it used to\n' + - '\n' + - 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + - ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' - ); -} +var insistOnInterVer = function () { + if (window.console) + console.warn( + 'You are using the MathQuill API without specifying an interface version, ' + + 'which will fail in v1.0.0. Easiest fix is to do the following before ' + + 'doing anything else:\n' + + '\n' + + ' MathQuill = MathQuill.getInterface(1);\n' + + ' // now MathQuill.MathField() works like it used to\n' + + '\n' + + 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + + ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' + ); +}; // globally exported API object -function MathQuill(el:HTMLElement) { +function MathQuill(el: HTMLElement) { insistOnInterVer(); return MQ1(el); -}; +} MathQuill.prototype = Progenote.prototype; -MathQuill.VERSION = "{VERSION}"; -MathQuill.interfaceVersion = function(v:number) { +MathQuill.VERSION = '{VERSION}'; +MathQuill.interfaceVersion = function (v: number) { // shim for #459-era interface versioning (ended with #495) if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v; - insistOnInterVer = function() { - if (window.console) console.warn( - 'You called MathQuill.interfaceVersion(1); to specify the interface ' + - 'version, which will fail in v1.0.0. You can fix this easily by doing ' + - 'this before doing anything else:\n' + - '\n' + - ' MathQuill = MathQuill.getInterface(1);\n' + - ' // now MathQuill.MathField() works like it used to\n' + - '\n' + - 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + - ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' - ); + insistOnInterVer = function () { + if (window.console) + console.warn( + 'You called MathQuill.interfaceVersion(1); to specify the interface ' + + 'version, which will fail in v1.0.0. You can fix this easily by doing ' + + 'this before doing anything else:\n' + + '\n' + + ' MathQuill = MathQuill.getInterface(1);\n' + + ' // now MathQuill.MathField() works like it used to\n' + + '\n' + + 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + + ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' + ); }; insistOnInterVer(); return MathQuill; }; MathQuill.getInterface = getInterface; -var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2; -function getInterface(v:number) { - if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' + - MIN + ' and ' + MAX + ' supported. You specified: ' + v; +var MIN = (getInterface.MIN = 1), + MAX = (getInterface.MAX = 2); +function getInterface(v: number) { + if (!(MIN <= v && v <= MAX)) + throw ( + 'Only interface versions between ' + + MIN + + ' and ' + + MAX + + ' supported. You specified: ' + + v + ); /** * Function that takes an HTML element and, if it's the root HTML element of a @@ -121,10 +133,12 @@ function getInterface(v:number) { * assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id); * */ - var MQ:MQ = function (el:HTMLElement) { + var MQ: MQ = function (el: HTMLElement) { if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the - // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92 - var blockNode = NodeBase.getNodeOfElement($(el).children('.mq-root-block')[0]) as MathBlock; // TODO - assumng it's a MathBlock + // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92 + var blockNode = NodeBase.getNodeOfElement( + $(el).children('.mq-root-block')[0] + ) as MathBlock; // TODO - assumng it's a MathBlock var ctrlr = blockNode && blockNode.controller; return ctrlr ? new APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null; }; @@ -133,18 +147,28 @@ function getInterface(v:number) { MQ.R = R; MQ.saneKeyboardEvents = saneKeyboardEvents; - function config(currentOptions:CursorOptions, newOptions:CursorOptions) { + function config(currentOptions: CursorOptions, newOptions: CursorOptions) { if (newOptions && newOptions.handlers) { - newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses }; - } - for (var name in newOptions) if (newOptions.hasOwnProperty(name)) { - var value = (newOptions as any)[name]; // TODO - think about typing this better - var processor = (optionProcessors as any)[name]; // TODO - validate option processors better - (currentOptions as any)[name] = (processor ? processor(value) : value); // TODO - think about typing better + newOptions.handlers = { + fns: newOptions.handlers, + APIClasses: APIClasses, + }; } + for (var name in newOptions) + if (newOptions.hasOwnProperty(name)) { + var value = (newOptions as any)[name]; // TODO - think about typing this better + var processor = (optionProcessors as any)[name]; // TODO - validate option processors better + (currentOptions as any)[name] = processor ? processor(value) : value; // TODO - think about typing better + } } - MQ.config = function(opts:CursorOptions) { config(Options.prototype, opts); return this; }; - MQ.registerEmbed = function(name:string, options:(data:EmbedOptionsData) => EmbedOptions) { + MQ.config = function (opts: CursorOptions) { + config(Options.prototype, opts); + return this; + }; + MQ.registerEmbed = function ( + name: string, + options: (data: EmbedOptionsData) => EmbedOptions + ) { if (!/^[a-z][a-z0-9]*$/i.test(name)) { throw 'Embed name must start with letter and be only letters and digits'; } @@ -152,21 +176,23 @@ function getInterface(v:number) { }; class AbstractMathQuill extends Progenote { - __controller:Controller; - __options:CursorOptions; - id:number; - data:ControllerData; + __controller: Controller; + __options: CursorOptions; + id: number; + data: ControllerData; revert?: () => $; - constructor (ctrlr:Controller) { + constructor(ctrlr: Controller) { super(); this.__controller = ctrlr; this.__options = ctrlr.options; this.id = ctrlr.id; this.data = ctrlr.data; - }; - __mathquillify (classNames:string) { - var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container; + } + __mathquillify(classNames: string) { + var ctrlr = this.__controller, + root = ctrlr.root, + el = ctrlr.container; ctrlr.createTextarea(); var contents = el.addClass(classNames).contents().detach(); @@ -174,17 +200,28 @@ function getInterface(v:number) { NodeBase.linkElementByBlockId(root.jQ[0], root.id); this.latex(contents.text()); - this.revert = function() { - return el.empty().unbind('.mathquill') - .removeClass('mq-editable-field mq-math-mode mq-text-mode') - .append(contents); + this.revert = function () { + return el + .empty() + .unbind('.mathquill') + .removeClass('mq-editable-field mq-math-mode mq-text-mode') + .append(contents); }; - }; - config (opts:CursorOptions) { config(this.__options, opts); return this; }; - el () { return this.__controller.container[0]; }; - text () { return this.__controller.exportText(); }; - mathspeak () { return this.__controller.exportMathSpeak(); }; - latex (latex:string) { + } + config(opts: CursorOptions) { + config(this.__options, opts); + return this; + } + el() { + return this.__controller.container[0]; + } + text() { + return this.__controller.exportText(); + } + mathspeak() { + return this.__controller.exportMathSpeak(); + } + latex(latex: string) { if (arguments.length > 0) { this.__controller.renderLatexMath(latex); const cursor = this.__controller.cursor; @@ -192,53 +229,61 @@ function getInterface(v:number) { return this; } return this.__controller.exportLatex(); - }; - html () { - return this.__controller.root.jQ.html() + } + html() { + return this.__controller.root.jQ + .html() .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '') .replace(/.?<\/span>/i, '') .replace(/ mq-hasCursor|mq-hasCursor ?/, '') .replace(/ class=(""|(?= |>))/g, ''); - }; - reflow () { - this.__controller.root.postOrder(function (node) { node.reflow(); }); + } + reflow() { + this.__controller.root.postOrder(function (node) { + node.reflow(); + }); return this; - }; - }; + } + } MQ.prototype = AbstractMathQuill.prototype; class EditableField extends AbstractMathQuill { - __mathquillify (classNames:string) { + __mathquillify(classNames: string) { super.__mathquillify(classNames); this.__controller.editable = true; this.__controller.delegateMouseEvents(); this.__controller.editablesTextareaEvents(); return this; - }; - focus () { + } + focus() { this.__controller.getTextareaOrThrow()[0].focus(); this.__controller.scrollHoriz(); return this; - }; - blur () { this.__controller.getTextareaOrThrow().blur(); return this; }; - write (latex:string) { + } + blur() { + this.__controller.getTextareaOrThrow().blur(); + return this; + } + write(latex: string) { this.__controller.writeLatex(latex); this.__controller.scrollHoriz(); const cursor = this.__controller.cursor; if (this.__controller.blurred) cursor.hide().parent.blur(cursor); return this; - }; - empty () { - var root = this.__controller.root, cursor = this.__controller.cursor; + } + empty() { + var root = this.__controller.root, + cursor = this.__controller.cursor; root.ends[L] = root.ends[R] = 0; root.jQ.empty(); delete cursor.selection; cursor.insAtRightEnd(root); return this; - }; - cmd (cmd:string) { - var ctrlr = this.__controller.notify(undefined), cursor = ctrlr.cursor; + } + cmd(cmd: string) { + var ctrlr = this.__controller.notify(undefined), + cursor = ctrlr.cursor; if (/^\\[a-z]+$/i.test(cmd) && !cursor.isTooDeep()) { cmd = cmd.slice(1); var klass = (LatexCmds as LatexCmdsAny)[cmd]; @@ -251,45 +296,48 @@ function getInterface(v:number) { } if (cursor.selection) node.replaces(cursor.replaceSelection()); node.createLeftOf(cursor.show()); - } - else /* TODO: API needs better error reporting */; - } - else cursor.parent.write(cursor, cmd); + } /* TODO: API needs better error reporting */ else; + } else cursor.parent.write(cursor, cmd); ctrlr.scrollHoriz(); if (ctrlr.blurred) cursor.hide().parent.blur(cursor); return this; - }; - select () { - var ctrlr = this.__controller; - ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root); - while (ctrlr.cursor[L]) ctrlr.selectLeft(); + } + select() { + this.__controller.selectAll(); return this; - }; - clearSelection () { + } + clearSelection() { this.__controller.cursor.clearSelection(); return this; - }; + } - moveToDirEnd (dir:Direction) { - this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root); + moveToDirEnd(dir: Direction) { + this.__controller + .notify('move') + .cursor.insAtDirEnd(dir, this.__controller.root); return this; - }; - moveToLeftEnd () { return this.moveToDirEnd(L); }; - moveToRightEnd () { return this.moveToDirEnd(R); }; + } + moveToLeftEnd() { + return this.moveToDirEnd(L); + } + moveToRightEnd() { + return this.moveToDirEnd(R); + } - keystroke (keysString:string, evt:KeyboardEvent) { + keystroke(keysString: string, evt: KeyboardEvent) { var keys = keysString.replace(/^\s+|\s+$/g, '').split(/\s+/); for (var i = 0; i < keys.length; i += 1) { this.__controller.keystroke(keys[i], evt || { preventDefault: noop }); } return this; - }; - typedText (text:string) { - for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i)); + } + typedText(text: string) { + for (var i = 0; i < text.length; i += 1) + this.__controller.typedText(text.charAt(i)); return this; - }; - dropEmbedded (pageX:number, pageY:number, options:EmbedOptions) { + } + dropEmbedded(pageX: number, pageY: number, options: EmbedOptions) { var clientX = pageX - $(window).scrollLeft(); var clientY = pageY - $(window).scrollTop(); @@ -297,41 +345,43 @@ function getInterface(v:number) { this.__controller.seek($(el), pageX, pageY); var cmd = new EmbedNode().setOptions(options); cmd.createLeftOf(this.__controller.cursor); - }; - setAriaLabel (ariaLabel:string) { + } + setAriaLabel(ariaLabel: string) { this.__controller.setAriaLabel(ariaLabel); return this; - }; - getAriaLabel () { + } + getAriaLabel() { return this.__controller.getAriaLabel(); - }; - setAriaPostLabel (ariaPostLabel:string, timeout:number) { + } + setAriaPostLabel(ariaPostLabel: string, timeout: number) { this.__controller.setAriaPostLabel(ariaPostLabel, timeout); return this; - }; - getAriaPostLabel () { + } + getAriaPostLabel() { return this.__controller.getAriaPostLabel(); - }; - clickAt (clientX:number, clientY:number, target:HTMLElement) { + } + clickAt(clientX: number, clientY: number, target: HTMLElement) { target = target || document.elementFromPoint(clientX, clientY); - var ctrlr = this.__controller, root = ctrlr.root; + var ctrlr = this.__controller, + root = ctrlr.root; if (!jQuery.contains(root.jQ[0], target)) target = root.jQ[0]; ctrlr.seek($(target), clientX + pageXOffset, clientY + pageYOffset); if (ctrlr.blurred) this.focus(); return this; - }; - ignoreNextMousedown (fn:CursorOptions['ignoreNextMousedown']) { + } + ignoreNextMousedown(fn: CursorOptions['ignoreNextMousedown']) { this.__controller.cursor.options.ignoreNextMousedown = fn; return this; - }; + } + } + MQ.EditableField = function () { + throw "wtf don't call me, I'm 'abstract'"; }; - MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; }; MQ.EditableField.prototype = EditableField.prototype; - - var APIClasses:APIClasses = { + var APIClasses: APIClasses = { AbstractMathQuill, - EditableField + EditableField, }; /** @@ -339,40 +389,53 @@ function getInterface(v:number) { * of each class. If the element had already been MathQuill-ified but into a * different kind (or it's not an HTML element), return null. */ - for (var kind in API) (function(kind, defAPIClass) { - var APIClass = APIClasses[kind] = defAPIClass(APIClasses); - MQ[kind] = function(el:HTMLElement, opts:CursorOptions) { - var mq = MQ(el); - if (mq instanceof APIClass || !el || !el.nodeType) return mq; - var ctrlr = new Controller(new APIClass.RootBlock(), $(el), new Options()); - ctrlr.KIND_OF_MQ = kind; - return new APIClass(ctrlr).__mathquillify(opts, v); - }; - MQ[kind].prototype = APIClass.prototype; - }(kind, API[kind])); + for (var kind in API) + (function (kind, defAPIClass) { + var APIClass = (APIClasses[kind] = defAPIClass(APIClasses)); + MQ[kind] = function (el: HTMLElement, opts: CursorOptions) { + var mq = MQ(el); + if (mq instanceof APIClass || !el || !el.nodeType) return mq; + var ctrlr = new Controller( + new APIClass.RootBlock(), + $(el), + new Options() + ); + ctrlr.KIND_OF_MQ = kind; + return new APIClass(ctrlr).__mathquillify(opts, v); + }; + MQ[kind].prototype = APIClass.prototype; + })(kind, API[kind]); return MQ; } -MathQuill.noConflict = function() { +MathQuill.noConflict = function () { window.MathQuill = origMathQuill; return MathQuill; }; var origMathQuill = window.MathQuill; window.MathQuill = MathQuill; -function RootBlockMixin(_:RootBlockMixinInput) { - _.moveOutOf = function (dir:Direction) { this.controller.handle('moveOutOf', dir) } - _.deleteOutOf = function (dir:Direction) { this.controller.handle('deleteOutOf', dir) } - _.selectOutOf = function (dir:Direction) { this.controller.handle('selectOutOf', dir) } - _.upOutOf = function (dir:Direction) { this.controller.handle('upOutOf', dir) } - _.downOutOf = function (dir:Direction) { this.controller.handle('downOutOf', dir) } +function RootBlockMixin(_: RootBlockMixinInput) { + _.moveOutOf = function (dir: Direction) { + this.controller.handle('moveOutOf', dir); + }; + _.deleteOutOf = function (dir: Direction) { + this.controller.handle('deleteOutOf', dir); + }; + _.selectOutOf = function (dir: Direction) { + this.controller.handle('selectOutOf', dir); + }; + _.upOutOf = function (dir: Direction) { + this.controller.handle('upOutOf', dir); + }; + _.downOutOf = function (dir: Direction) { + this.controller.handle('downOutOf', dir); + }; - _.reflow = function() { + _.reflow = function () { this.controller.handle('reflow'); this.controller.handle('edited'); this.controller.handle('edit'); }; } - - diff --git a/src/services/aria.ts b/src/services/aria.ts index 3a5fb903a..67bc5afa9 100755 --- a/src/services/aria.ts +++ b/src/services/aria.ts @@ -14,35 +14,38 @@ type AriaQueueItem = NodeRef | Fragment | string; class Aria { - controller:Controller; - jQ = jQuery(''); + controller: Controller; + jQ = jQuery( + '' + ); msg = ''; - items:AriaQueueItem[] = []; + items: AriaQueueItem[] = []; - constructor (controller:Controller) { + constructor(controller: Controller) { this.controller = controller; - }; + } - setContainer(el:$) { + setContainer(el: $) { this.jQ.appendTo(el); - }; + } - queue (item:AriaQueueItem, shouldDescribe:boolean = false) { - var output:Fragment | string = ''; + queue(item: AriaQueueItem, shouldDescribe: boolean = false) { + var output: Fragment | string = ''; if (item instanceof MQNode) { // Some constructs include verbal shorthand (such as simple fractions and exponents). // Since ARIA alerts relate to moving through interactive content, we don't want to use that shorthand if it exists // since doing so may be ambiguous or confusing. - var itemMathspeak = item.mathspeak({ignoreShorthand: true}); - if (shouldDescribe) { // used to ensure item is described when cursor reaches block boundaries + var itemMathspeak = item.mathspeak({ ignoreShorthand: true }); + if (shouldDescribe) { + // used to ensure item is described when cursor reaches block boundaries if ( item.parent && item.parent.ariaLabel && item.ariaLabel === 'block' ) { - output = item.parent.ariaLabel+' '+itemMathspeak; + output = item.parent.ariaLabel + ' ' + itemMathspeak; } else if (item.ariaLabel) { - output = item.ariaLabel+' '+itemMathspeak; + output = item.ariaLabel + ' ' + itemMathspeak; } } if (output === '') { @@ -53,33 +56,36 @@ class Aria { } this.items.push(output); return this; - }; - queueDirOf (dir:Direction) { + } + queueDirOf(dir: Direction) { prayDirection(dir); return this.queue(dir === L ? 'before' : 'after'); - }; - queueDirEndOf (dir:Direction) { + } + queueDirEndOf(dir: Direction) { prayDirection(dir); return this.queue(dir === L ? 'beginning of' : 'end of'); - }; + } - alert (t?:AriaQueueItem) { + alert(t?: AriaQueueItem) { if (t) this.queue(t); if (this.items.length) { // To cut down on potential verbiage from multiple Mathquills firing near-simultaneous ARIA alerts, // update the text of this instance if its container also has keyboard focus. // If it does not, leave the DOM unchanged but flush the queue regardless. // Note: updating the msg variable regardless of focus for unit tests. - this.msg = this.items.join(' ').replace(/ +(?= )/g,'').trim(); + this.msg = this.items + .join(' ') + .replace(/ +(?= )/g, '') + .trim(); if (this.controller.containerHasFocus()) { this.jQ.empty().text(this.msg); } } return this.clear(); - }; + } - clear () { + clear() { this.items.length = 0; return this; - }; -}; + } +} diff --git a/src/services/exportText.ts b/src/services/exportText.ts index 56e58b862..65cb63e97 100644 --- a/src/services/exportText.ts +++ b/src/services/exportText.ts @@ -4,9 +4,9 @@ **********************************************/ class Controller_exportText extends ControllerBase { - exportText () { - return this.root.foldChildren('', function(text, child) { + exportText() { + return this.root.foldChildren('', function (text, child) { return text + child.text(); }); - }; -}; + } +} diff --git a/src/services/focusBlur.ts b/src/services/focusBlur.ts index e7fba2b50..ec7c810f4 100644 --- a/src/services/focusBlur.ts +++ b/src/services/focusBlur.ts @@ -16,11 +16,11 @@ ControllerBase.onNotify(function (cursor, e) { }); class Controller_focusBlur extends Controller_exportText { - blurred:boolean; - __disableGroupingTimeout:number; - textareaSelectionTimeout:number; + blurred: boolean; + __disableGroupingTimeout: number; + textareaSelectionTimeout: number; - disableGroupingForSeconds (seconds:number) { + disableGroupingForSeconds(seconds: number) { clearTimeout(this.__disableGroupingTimeout); var jQ = this.root.jQ; @@ -34,47 +34,55 @@ class Controller_focusBlur extends Controller_exportText { } } - focusBlurEvents () { - var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor; - var blurTimeout:number; + focusBlurEvents() { + var ctrlr = this, + root = ctrlr.root, + cursor = ctrlr.cursor; + var blurTimeout: number; const textarea = ctrlr.getTextareaOrThrow(); - textarea.focus(function() { - ctrlr.updateMathspeak(); - ctrlr.blurred = false; - clearTimeout(blurTimeout); - ctrlr.container.addClass('mq-focused'); - if (!cursor.parent) cursor.insAtRightEnd(root); - if (cursor.selection) { - cursor.selection.jQ.removeClass('mq-blur'); - ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back - } else { - cursor.show(); - } - ctrlr.setOverflowClasses(); - - }).blur(function() { - if (ctrlr.textareaSelectionTimeout) { - clearTimeout(ctrlr.textareaSelectionTimeout); - ctrlr.textareaSelectionTimeout = 0; - } - ctrlr.disableGroupingForSeconds(0); - ctrlr.blurred = true; - blurTimeout = setTimeout(function() { // wait for blur on window; if - root.postOrder(function (node) { node.intentionalBlur(); }); // none, intentional blur: #264 - cursor.clearSelection().endSelection(); - blur(); + textarea + .focus(function () { ctrlr.updateMathspeak(); - ctrlr.scrollHoriz(); + ctrlr.blurred = false; + clearTimeout(blurTimeout); + ctrlr.container.addClass('mq-focused'); + if (!cursor.parent) cursor.insAtRightEnd(root); + if (cursor.selection) { + cursor.selection.jQ.removeClass('mq-blur'); + ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back + } else { + cursor.show(); + } + ctrlr.setOverflowClasses(); + }) + .blur(function () { + if (ctrlr.textareaSelectionTimeout) { + clearTimeout(ctrlr.textareaSelectionTimeout); + ctrlr.textareaSelectionTimeout = 0; + } + ctrlr.disableGroupingForSeconds(0); + ctrlr.blurred = true; + blurTimeout = setTimeout(function () { + // wait for blur on window; if + root.postOrder(function (node) { + node.intentionalBlur(); + }); // none, intentional blur: #264 + cursor.clearSelection().endSelection(); + blur(); + ctrlr.updateMathspeak(); + ctrlr.scrollHoriz(); + }); + $(window).bind('blur', windowBlur); }); - $(window).bind('blur', windowBlur); - }); - function windowBlur() { // blur event also fired on window, just switching + function windowBlur() { + // blur event also fired on window, just switching clearTimeout(blurTimeout); // tabs/windows, not intentional blur if (cursor.selection) cursor.selection.jQ.addClass('mq-blur'); blur(); ctrlr.updateMathspeak(); } - function blur() { // not directly in the textarea blur handler so as to be + function blur() { + // not directly in the textarea blur handler so as to be cursor.hide().parent.blur(cursor); // synchronous with/in the same frame as ctrlr.container.removeClass('mq-focused'); // clearing/blurring selection $(window).unbind('blur', windowBlur); @@ -85,12 +93,12 @@ class Controller_focusBlur extends Controller_exportText { } ctrlr.blurred = true; cursor.hide().parent.blur(cursor); - }; - unbindFocusBlurEvents () { + } + unbindFocusBlurEvents() { var textarea = this.getTextareaOrThrow(); textarea.unbind('focus blur'); - }; -}; + } +} /** * TODO: I wanted to move MathBlock::focus and blur here, it would clean diff --git a/src/services/keystroke.ts b/src/services/keystroke.ts index 8b4fcb949..1c00be6c7 100644 --- a/src/services/keystroke.ts +++ b/src/services/keystroke.ts @@ -2,219 +2,272 @@ * Deals with the browser DOM events from * interaction with the typist. ****************************************/ - class MQNode extends NodeBase { - keystroke (key:string, e:KeyboardEvent, ctrlr:Controller) { - var cursor = ctrlr.cursor; - - switch (key) { - case 'Ctrl-Shift-Backspace': - case 'Ctrl-Backspace': - ctrlr.ctrlDeleteDir(L); - break; - - case 'Shift-Backspace': - case 'Backspace': - ctrlr.backspace(); - break; - - // Tab or Esc -> go one block right if it exists, else escape right. - case 'Esc': - case 'Tab': - ctrlr.escapeDir(R, key, e); - return; - - // Shift-Tab -> go one block left if it exists, else escape left. - case 'Shift-Tab': - case 'Shift-Esc': - ctrlr.escapeDir(L, key, e); - return; - - // End -> move to the end of the current block. - case 'End': - ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent); - ctrlr.aria.queue("end of").queue(cursor.parent, true); - break; - - // Ctrl-End -> move all the way to the end of the root block. - case 'Ctrl-End': - ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root); - ctrlr.aria.queue("end of").queue(ctrlr.ariaLabel).queue(ctrlr.root).queue(ctrlr.ariaPostLabel); - break; - - // Shift-End -> select to the end of the current block. - case 'Shift-End': - while (cursor[R]) { - ctrlr.selectRight(); - } - break; - - // Ctrl-Shift-End -> select all the way to the end of the root block. - case 'Ctrl-Shift-End': - while (cursor[R] || cursor.parent !== ctrlr.root) { - ctrlr.selectRight(); - } - break; - - // Home -> move to the start of the current block. - case 'Home': - ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent); - ctrlr.aria.queue("beginning of").queue(cursor.parent, true); - break; - - // Ctrl-Home -> move all the way to the start of the root block. - case 'Ctrl-Home': - ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root); - ctrlr.aria.queue("beginning of").queue(ctrlr.ariaLabel).queue(ctrlr.root).queue(ctrlr.ariaPostLabel); - break; - - // Shift-Home -> select to the start of the current block. - case 'Shift-Home': - while (cursor[L]) { - ctrlr.selectLeft(); - } - break; - - // Ctrl-Shift-Home -> select all the way to the start of the root block. - case 'Ctrl-Shift-Home': - while (cursor[L] || cursor.parent !== ctrlr.root) { - ctrlr.selectLeft(); - } - break; - - case 'Left': ctrlr.moveLeft(); break; - case 'Shift-Left': ctrlr.selectLeft(); break; - case 'Ctrl-Left': break; - case 'Right': ctrlr.moveRight(); break; - case 'Shift-Right': ctrlr.selectRight(); break; - case 'Ctrl-Right': break; +/** + * Only one incremental selection may be open at a time. Track whether + * an incremental selection is open to help enforce this invariant. + */ +var INCREMENTAL_SELECTION_OPEN = false; - case 'Up': ctrlr.moveUp(); break; - case 'Down': ctrlr.moveDown(); break; +class MQNode extends NodeBase { + keystroke(key: string, e: KeyboardEvent, ctrlr: Controller) { + var cursor = ctrlr.cursor; - case 'Shift-Up': - if (cursor[L]) { - while (cursor[L]) ctrlr.selectLeft(); - } else { + switch (key) { + case 'Ctrl-Shift-Backspace': + case 'Ctrl-Backspace': + ctrlr.ctrlDeleteDir(L); + break; + + case 'Shift-Backspace': + case 'Backspace': + ctrlr.backspace(); + break; + + // Tab or Esc -> go one block right if it exists, else escape right. + case 'Esc': + case 'Tab': + ctrlr.escapeDir(R, key, e); + return; + + // Shift-Tab -> go one block left if it exists, else escape left. + case 'Shift-Tab': + case 'Shift-Esc': + ctrlr.escapeDir(L, key, e); + return; + + // End -> move to the end of the current block. + case 'End': + ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent); + ctrlr.aria.queue('end of').queue(cursor.parent, true); + break; + + // Ctrl-End -> move all the way to the end of the root block. + case 'Ctrl-End': + ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root); + ctrlr.aria + .queue('end of') + .queue(ctrlr.ariaLabel) + .queue(ctrlr.root) + .queue(ctrlr.ariaPostLabel); + break; + + // Shift-End -> select to the end of the current block. + case 'Shift-End': + ctrlr.selectToBlockEndInDir(R); + break; + + // Ctrl-Shift-End -> select all the way to the end of the root block. + case 'Ctrl-Shift-End': + ctrlr.selectToRootEndInDir(R); + break; + + // Home -> move to the start of the current block. + case 'Home': + ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent); + ctrlr.aria.queue('beginning of').queue(cursor.parent, true); + break; + + // Ctrl-Home -> move all the way to the start of the root block. + case 'Ctrl-Home': + ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root); + ctrlr.aria + .queue('beginning of') + .queue(ctrlr.ariaLabel) + .queue(ctrlr.root) + .queue(ctrlr.ariaPostLabel); + break; + + // Shift-Home -> select to the start of the current block. + case 'Shift-Home': + ctrlr.selectToBlockEndInDir(L); + break; + + // Ctrl-Shift-Home -> select all the way to the start of the root block. + case 'Ctrl-Shift-Home': + ctrlr.selectToRootEndInDir(L); + break; + + case 'Left': + ctrlr.moveLeft(); + break; + case 'Shift-Left': ctrlr.selectLeft(); - } - break; - - case 'Shift-Down': - if (cursor[R]) { - while (cursor[R]) ctrlr.selectRight(); - } - else { + break; + case 'Ctrl-Left': + break; + + case 'Right': + ctrlr.moveRight(); + break; + case 'Shift-Right': ctrlr.selectRight(); - } - break; - - case 'Ctrl-Up': break; - case 'Ctrl-Down': break; - - case 'Ctrl-Shift-Del': - case 'Ctrl-Del': - ctrlr.ctrlDeleteDir(R); - break; - - case 'Shift-Del': - case 'Del': - ctrlr.deleteForward(); - break; - - case 'Meta-A': - case 'Ctrl-A': - ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root); - while (cursor[L]) ctrlr.selectLeft(); - break; - - // These remaining hotkeys are only of benefit to people running screen readers. - case 'Ctrl-Alt-Up': // speak parent block that has focus - if (cursor.parent.parent && cursor.parent.parent instanceof MQNode) ctrlr.aria.queue(cursor.parent.parent); - else ctrlr.aria.queue('nothing above'); - break; - - case 'Ctrl-Alt-Down': // speak current block that has focus - if (cursor.parent && cursor.parent instanceof MQNode) ctrlr.aria.queue(cursor.parent); - else ctrlr.aria.queue('block is empty'); - break; - - case 'Ctrl-Alt-Left': // speak left-adjacent block - if ( - cursor.parent.parent && - cursor.parent.parent.ends && - cursor.parent.parent.ends[L] && - cursor.parent.parent.ends[L] instanceof MQNode - ) { - ctrlr.aria.queue(cursor.parent.parent.ends[L]); - } else { - ctrlr.aria.queue('nothing to the left'); - } - break; - - case 'Ctrl-Alt-Right': // speak right-adjacent block - if ( - cursor.parent.parent && - cursor.parent.parent.ends && - cursor.parent.parent.ends[R] && - cursor.parent.parent.ends[R] instanceof MQNode - ) { - ctrlr.aria.queue(cursor.parent.parent.ends[R]); - } else { - ctrlr.aria.queue('nothing to the right'); - } - break; - - case 'Ctrl-Alt-Shift-Down': // speak selection - if (cursor.selection) ctrlr.aria.queue(cursor.selection.join('mathspeak', ' ').trim() + ' selected'); - else ctrlr.aria.queue('nothing selected'); - break; - - case 'Ctrl-Alt-=': - case 'Ctrl-Alt-Shift-Right': // speak ARIA post label (evaluation or error) - if (ctrlr.ariaPostLabel.length) ctrlr.aria.queue(ctrlr.ariaPostLabel); - else ctrlr.aria.queue('no answer'); - break; - - default: - return; + break; + case 'Ctrl-Right': + break; + + case 'Up': + ctrlr.moveUp(); + break; + case 'Down': + ctrlr.moveDown(); + break; + + case 'Shift-Up': + ctrlr.withIncrementalSelection((selectDir) => { + if (cursor[L]) { + while (cursor[L]) selectDir(L); + } else { + selectDir(L); + } + }); + break; + + case 'Shift-Down': + ctrlr.withIncrementalSelection((selectDir) => { + if (cursor[R]) { + while (cursor[R]) selectDir(R); + } else { + selectDir(R); + } + }); + break; + + case 'Ctrl-Up': + break; + case 'Ctrl-Down': + break; + + case 'Ctrl-Shift-Del': + case 'Ctrl-Del': + ctrlr.ctrlDeleteDir(R); + break; + + case 'Shift-Del': + case 'Del': + ctrlr.deleteForward(); + break; + + case 'Meta-A': + case 'Ctrl-A': + ctrlr.selectAll(); + break; + + // These remaining hotkeys are only of benefit to people running screen readers. + case 'Ctrl-Alt-Up': // speak parent block that has focus + if (cursor.parent.parent && cursor.parent.parent instanceof MQNode) + ctrlr.aria.queue(cursor.parent.parent); + else ctrlr.aria.queue('nothing above'); + break; + + case 'Ctrl-Alt-Down': // speak current block that has focus + if (cursor.parent && cursor.parent instanceof MQNode) + ctrlr.aria.queue(cursor.parent); + else ctrlr.aria.queue('block is empty'); + break; + + case 'Ctrl-Alt-Left': // speak left-adjacent block + if ( + cursor.parent.parent && + cursor.parent.parent.ends && + cursor.parent.parent.ends[L] && + cursor.parent.parent.ends[L] instanceof MQNode + ) { + ctrlr.aria.queue(cursor.parent.parent.ends[L]); + } else { + ctrlr.aria.queue('nothing to the left'); + } + break; + + case 'Ctrl-Alt-Right': // speak right-adjacent block + if ( + cursor.parent.parent && + cursor.parent.parent.ends && + cursor.parent.parent.ends[R] && + cursor.parent.parent.ends[R] instanceof MQNode + ) { + ctrlr.aria.queue(cursor.parent.parent.ends[R]); + } else { + ctrlr.aria.queue('nothing to the right'); + } + break; + + case 'Ctrl-Alt-Shift-Down': // speak selection + if (cursor.selection) + ctrlr.aria.queue( + cursor.selection.join('mathspeak', ' ').trim() + ' selected' + ); + else ctrlr.aria.queue('nothing selected'); + break; + + case 'Ctrl-Alt-=': + case 'Ctrl-Alt-Shift-Right': // speak ARIA post label (evaluation or error) + if (ctrlr.ariaPostLabel.length) ctrlr.aria.queue(ctrlr.ariaPostLabel); + else ctrlr.aria.queue('no answer'); + break; + + default: + return; } ctrlr.aria.alert(); e.preventDefault(); ctrlr.scrollHoriz(); - }; - - moveOutOf (_dir:Direction, _cursor:Cursor, _updown?:'up' | 'down') { pray('overridden or never called on this node'); } // called by Controller::escapeDir, moveDir - moveTowards (_dir:Direction, _cursor:Cursor, _updown?:'up' | 'down') { pray('overridden or never called on this node'); } // called by Controller::moveDir - deleteOutOf (_dir:Direction, _cursor:Cursor) { pray('overridden or never called on this node'); } // called by Controller::deleteDir - deleteTowards (_dir:Direction, _cursor:Cursor) { pray('overridden or never called on this node'); } // called by Controller::deleteDir - unselectInto (_dir:Direction, _cursor:Cursor) { pray('overridden or never called on this node'); } // called by Controller::selectDir - selectOutOf (_dir:Direction, _cursor:Cursor) { pray('overridden or never called on this node'); } // called by Controller::selectDir - selectTowards (_dir:Direction, _cursor:Cursor) { pray('overridden or never called on this node'); } // called by Controller::selectDir + } + + moveOutOf(_dir: Direction, _cursor: Cursor, _updown?: 'up' | 'down') { + pray('overridden or never called on this node'); + } // called by Controller::escapeDir, moveDir + moveTowards(_dir: Direction, _cursor: Cursor, _updown?: 'up' | 'down') { + pray('overridden or never called on this node'); + } // called by Controller::moveDir + deleteOutOf(_dir: Direction, _cursor: Cursor) { + pray('overridden or never called on this node'); + } // called by Controller::deleteDir + deleteTowards(_dir: Direction, _cursor: Cursor) { + pray('overridden or never called on this node'); + } // called by Controller::deleteDir + unselectInto(_dir: Direction, _cursor: Cursor) { + pray('overridden or never called on this node'); + } // called by Controller::selectDir + selectOutOf(_dir: Direction, _cursor: Cursor) { + pray('overridden or never called on this node'); + } // called by Controller::selectDir + selectTowards(_dir: Direction, _cursor: Cursor) { + pray('overridden or never called on this node'); + } // called by Controller::selectDir } -ControllerBase.onNotify(function(cursor:Cursor, e:ControllerEvent) { +ControllerBase.onNotify(function (cursor: Cursor, e: ControllerEvent) { if (e === 'move' || e === 'upDown') cursor.show().clearSelection(); }); -optionProcessors.leftRightIntoCmdGoes = function(updown:'up'|'down') { +optionProcessors.leftRightIntoCmdGoes = function (updown: 'up' | 'down') { if (updown && updown !== 'up' && updown !== 'down') { - throw '"up" or "down" required for leftRightIntoCmdGoes option, ' - + 'got "'+updown+'"'; + throw ( + '"up" or "down" required for leftRightIntoCmdGoes option, ' + + 'got "' + + updown + + '"' + ); } return updown; }; - -ControllerBase.onNotify(function(cursor:Cursor, e:ControllerEvent) { if (e !== 'upDown') cursor.upDownCache = {}; }); -ControllerBase.onNotify(function(cursor:Cursor, e:ControllerEvent) { if (e === 'edit') cursor.show().deleteSelection(); }); -ControllerBase.onNotify(function(cursor:Cursor, e:ControllerEvent) { if (e !== 'select') cursor.endSelection(); }); +ControllerBase.onNotify(function (cursor: Cursor, e: ControllerEvent) { + if (e !== 'upDown') cursor.upDownCache = {}; +}); +ControllerBase.onNotify(function (cursor: Cursor, e: ControllerEvent) { + if (e === 'edit') cursor.show().deleteSelection(); +}); +ControllerBase.onNotify(function (cursor: Cursor, e: ControllerEvent) { + if (e !== 'select') cursor.endSelection(); +}); class Controller_keystroke extends Controller_focusBlur { - keystroke (key:string, evt:KeyboardEvent) { + keystroke(key: string, evt: KeyboardEvent) { this.cursor.parent.keystroke(key, evt, this.getControllerSelf()); - }; + } - escapeDir (dir:Direction, _key:string, e:KeyboardEvent) { + escapeDir(dir: Direction, _key: string, e: KeyboardEvent) { prayDirection(dir); var cursor = this.cursor; @@ -228,22 +281,26 @@ class Controller_keystroke extends Controller_focusBlur { cursor.parent.moveOutOf(dir, cursor); cursor.controller.aria.alert(); return this.notify('move'); - }; - moveDir (dir:Direction) { + } + moveDir(dir: Direction) { prayDirection(dir); - var cursor = this.cursor, updown = cursor.options.leftRightIntoCmdGoes; + var cursor = this.cursor, + updown = cursor.options.leftRightIntoCmdGoes; var cursorDir = cursor[dir]; if (cursor.selection) { cursor.insDirOf(dir, cursor.selection.ends[dir] as MQNode); - } - else if (cursorDir) cursorDir.moveTowards(dir, cursor, updown); + } else if (cursorDir) cursorDir.moveTowards(dir, cursor, updown); else cursor.parent.moveOutOf(dir, cursor, updown); return this.notify('move'); - }; - moveLeft () { return this.moveDir(L); }; - moveRight () { return this.moveDir(R); }; + } + moveLeft() { + return this.moveDir(L); + } + moveRight() { + return this.moveDir(R); + } /** * moveUp and moveDown have almost identical algorithms: @@ -257,13 +314,17 @@ class Controller_keystroke extends Controller_focusBlur { * as close to directly above/below the current position as possible) * + unless it's exactly `true`, stop bubbling */ - moveUp () { return this.moveUpDown('up'); }; - moveDown () { return this.moveUpDown('down'); }; - moveUpDown (dir:'up'|'down') { + moveUp() { + return this.moveUpDown('up'); + } + moveDown() { + return this.moveUpDown('down'); + } + moveUpDown(dir: 'up' | 'down') { var self = this; var cursor = self.notify('upDown').cursor; - var dirInto:'upInto' | 'downInto'; - var dirOutOf:'upOutOf' | 'downOutOf'; + var dirInto: 'upInto' | 'downInto'; + var dirOutOf: 'upOutOf' | 'downOutOf'; if (dir === 'up') { dirInto = 'upInto'; @@ -281,39 +342,50 @@ class Controller_keystroke extends Controller_focusBlur { if (cursorR_dirInto) cursor.insAtLeftEnd(cursorR_dirInto); else if (cursorL_dirInto) cursor.insAtRightEnd(cursorL_dirInto); else { - cursor.parent.bubble(function(ancestor:MQNode) { // TODO - revist this + cursor.parent.bubble(function (ancestor: MQNode) { + // TODO - revist this var prop = ancestor[dirOutOf]; if (prop) { - if (typeof prop === 'function') prop = prop.call(ancestor,cursor) as any; // TODO - figure out if we need to assign to prop + if (typeof prop === 'function') + prop = prop.call(ancestor, cursor) as any; // TODO - figure out if we need to assign to prop if (prop instanceof MQNode) cursor.jumpUpDown(ancestor, prop); - if (prop as any !== true) return false; // TODO - figure out how this can return true + if ((prop as any) !== true) return false; // TODO - figure out how this can return true } return undefined; }); } return self; } - deleteDir (dir:Direction) { + deleteDir(dir: Direction) { prayDirection(dir); var cursor = this.cursor; var cursorEl = cursor[dir] as MQNode; var cursorElParent = cursor.parent.parent; var ctrlr = cursor.controller; - if(cursorEl && cursorEl instanceof MQNode) { - if(cursorEl.sides ) { - ctrlr.aria.queue(cursorEl.parent.chToCmd(cursorEl.sides[-dir as Direction].ch).mathspeak({createdLeftOf: cursor})); - // generally, speak the current element if it has no blocks, - // but don't for text block commands as the deleteTowards method - // in the TextCommand class is responsible for speaking the new character under the cursor. + if (cursorEl && cursorEl instanceof MQNode) { + if (cursorEl.sides) { + ctrlr.aria.queue( + cursorEl.parent + .chToCmd(cursorEl.sides[-dir as Direction].ch) + .mathspeak({ createdLeftOf: cursor }) + ); + // generally, speak the current element if it has no blocks, + // but don't for text block commands as the deleteTowards method + // in the TextCommand class is responsible for speaking the new character under the cursor. } else if (!cursorEl.blocks && cursorEl.parent.ctrlSeq !== '\\text') { ctrlr.aria.queue(cursorEl); } - } else if(cursorElParent && cursorElParent instanceof MQNode) { - if(cursorElParent.sides) { - ctrlr.aria.queue(cursorElParent.parent.chToCmd(cursorElParent.sides[dir].ch).mathspeak({createdLeftOf: cursor})); + } else if (cursorElParent && cursorElParent instanceof MQNode) { + if (cursorElParent.sides) { + ctrlr.aria.queue( + cursorElParent.parent + .chToCmd(cursorElParent.sides[dir].ch) + .mathspeak({ createdLeftOf: cursor }) + ); } else if (cursorElParent.blocks && cursorElParent.mathspeakTemplate) { - if (cursorElParent.upInto && cursorElParent.downInto) { // likely a fraction, and we just backspaced over the slash + if (cursorElParent.upInto && cursorElParent.downInto) { + // likely a fraction, and we just backspaced over the slash ctrlr.aria.queue(cursorElParent.mathspeakTemplate[1]); } else { var mst = cursorElParent.mathspeakTemplate; @@ -343,8 +415,8 @@ class Controller_keystroke extends Controller_focusBlur { }); return this; - }; - ctrlDeleteDir (dir:Direction) { + } + ctrlDeleteDir(dir: Direction) { prayDirection(dir); var cursor = this.cursor; if (!cursor[dir] || cursor.selection) return this.deleteDir(dir); @@ -352,9 +424,15 @@ class Controller_keystroke extends Controller_focusBlur { this.notify('edit'); var fragRemoved; if (dir === L) { - fragRemoved = new Fragment((cursor.parent as MQNode).ends[L] as MQNode, cursor[L] as MQNode); + fragRemoved = new Fragment( + (cursor.parent as MQNode).ends[L] as MQNode, + cursor[L] as MQNode + ); } else { - fragRemoved = new Fragment(cursor[R] as MQNode, (cursor.parent as MQNode).ends[R] as MQNode); + fragRemoved = new Fragment( + cursor[R] as MQNode, + (cursor.parent as MQNode).ends[R] as MQNode + ); } cursor.controller.aria.queue(fragRemoved); fragRemoved.remove(); @@ -371,35 +449,150 @@ class Controller_keystroke extends Controller_focusBlur { }); return this; - }; - backspace () { return this.deleteDir(L); }; - deleteForward () { return this.deleteDir(R); }; - - selectDir (dir:Direction) { - var cursor = this.notify('select').cursor, seln = cursor.selection; - prayDirection(dir); + } + backspace() { + return this.deleteDir(L); + } + deleteForward() { + return this.deleteDir(R); + } + /** + * startIncrementalSelection, selectDirIncremental, and finishIncrementalSelection + * should only be called by withIncrementalSelection because they must + * be called in sequence. + */ + private startIncrementalSelection() { + pray( + "Multiple selections can't be simultaneously open", + !INCREMENTAL_SELECTION_OPEN + ); + + INCREMENTAL_SELECTION_OPEN = true; + this.notify('select'); + var cursor = this.cursor; if (!cursor.anticursor) cursor.startSelection(); + } + + /** + * Update the selection model, stored in cursor, without modifying + * selection DOM. + * + * startIncrementalSelection, selectDirIncremental, and finishIncrementalSelection + * should only be called by withIncrementalSelection because they must + * be called in sequence. + */ + private selectDirIncremental(dir: Direction) { + pray('A selection is open', INCREMENTAL_SELECTION_OPEN); + INCREMENTAL_SELECTION_OPEN = true; + + var cursor = this.cursor, + seln = cursor.selection; + prayDirection(dir); var node = cursor[dir]; if (node) { // "if node we're selecting towards is inside selection (hence retracting) // and is on the *far side* of the selection (hence is only node selected) // and the anticursor is *inside* that node, not just on the other side" - if (seln && seln.ends[dir] === node && (cursor.anticursor as Anticursor)[-dir as Direction] !== node) { + if ( + seln && + seln.ends[dir] === node && + (cursor.anticursor as Anticursor)[-dir as Direction] !== node + ) { node.unselectInto(dir, cursor); - } - else node.selectTowards(dir, cursor); - } - else cursor.parent.selectOutOf(dir, cursor); + } else node.selectTowards(dir, cursor); + } else cursor.parent.selectOutOf(dir, cursor); + } + /** + * Update selection DOM to match cursor model + * + * startIncrementalSelection, selectDirIncremental, and finishIncrementalSelection + * should only be called by withIncrementalSelection because they must + * be called in sequence. + */ + private finishIncrementalSelection() { + pray('A selection is open', INCREMENTAL_SELECTION_OPEN); + var cursor = this.cursor; cursor.clearSelection(); cursor.select() || cursor.show(); var selection = cursor.selection; if (selection) { - cursor.controller.aria.clear().queue(selection.join('mathspeak', ' ').trim() + ' selected'); // clearing first because selection fires several times, and we don't want repeated speech. + cursor.controller.aria + .clear() + .queue(selection.join('mathspeak', ' ').trim() + ' selected'); // clearing first because selection fires several times, and we don't want repeated speech. } - }; - selectLeft () { return this.selectDir(L); }; - selectRight () { return this.selectDir(R); }; -}; + INCREMENTAL_SELECTION_OPEN = false; + } + + /** + * Used to build a selection incrementally in a loop. Calls the passed + * callback with a selectDir function that may be called many times, + * and defers updating the view until the incremental selection is + * complete + * + * Wraps up calling + * + * this.startSelection() + * this.selectDirIncremental(dir) // possibly many times + * this.finishSelection() + * + * with extra error handling and invariant enforcement + */ + withIncrementalSelection(cb: (selectDir: (dir: Direction) => void) => void) { + try { + this.startIncrementalSelection(); + try { + cb((dir) => this.selectDirIncremental(dir)); + } finally { + // Since we have started a selection, attempt to finish it even + // if the callback throws an error + this.finishIncrementalSelection(); + } + } finally { + // Mark selection as closed even if finishSelection throws an + // error. Makes a possible error in finishSelection more + // recoverable + INCREMENTAL_SELECTION_OPEN = false; + } + } + + /** + * Grow selection one unit in the given direction + * + * Note, this should not be called in a loop. To incrementally grow a + * selection, use withIncrementalSelection + */ + selectDir(dir: Direction) { + this.withIncrementalSelection((selectDir) => selectDir(dir)); + } + selectLeft() { + return this.selectDir(L); + } + selectRight() { + return this.selectDir(R); + } + selectAll() { + this.notify('move'); + const cursor = this.cursor; + cursor.insAtRightEnd(this.root); + this.withIncrementalSelection((selectDir) => { + while (cursor[L]) selectDir(L); + }); + } + selectToBlockEndInDir(dir: Direction) { + const cursor = this.cursor; + this.withIncrementalSelection((selectDir) => { + while (cursor[dir]) selectDir(dir); + }); + } + selectToRootEndInDir(dir: Direction) { + const cursor = this.cursor; + this.withIncrementalSelection((selectDir) => { + while (cursor[dir] || cursor.parent !== this.root) { + selectDir(dir); + } + }); + } +} diff --git a/src/services/latex.ts b/src/services/latex.ts index b6cbc0346..290a611e9 100644 --- a/src/services/latex.ts +++ b/src/services/latex.ts @@ -1,17 +1,18 @@ class TempSingleCharNode extends MQNode { - constructor (_char:string) { + constructor(_char: string) { super(); } } // Parser MathBlock -var latexMathParser = (function() { - function commandToBlock(cmd:MQNode | Fragment):MathBlock { // can also take in a Fragment +var latexMathParser = (function () { + function commandToBlock(cmd: MQNode | Fragment): MathBlock { + // can also take in a Fragment var block = new MathBlock(); cmd.adopt(block, 0, 0); return block; } - function joinBlocks(blocks:MathBlock[]) { + function joinBlocks(blocks: MathBlock[]) { var firstBlock = blocks[0] || new MathBlock(); for (var i = 1; i < blocks.length; i += 1) { @@ -32,18 +33,26 @@ var latexMathParser = (function() { // Parsers yielding either MathCommands, or Fragments of MathCommands // (either way, something that can be adopted by a MathBlock) - var variable = letter.map(function(c) { return new Letter(c); }); - var number = digit.map(function (c) { return new Digit(c); }); - var symbol = regex(/^[^${}\\_^]/).map(function(c) { return new VanillaSymbol(c); }); - - var controlSequence = - regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write - .or(string('\\').then( - regex(/^[a-z]+/i) - .or(regex(/^\s+/).result(' ')) - .or(any) - )) - .then(function(ctrlSeq):Parser { // TODO - is Parser correct? + var variable = letter.map(function (c) { + return new Letter(c); + }); + var number = digit.map(function (c) { + return new Digit(c); + }); + var symbol = regex(/^[^${}\\_^]/).map(function (c) { + return new VanillaSymbol(c); + }); + + var controlSequence = regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write + .or( + string('\\').then( + regex(/^[a-z]+/i) + .or(regex(/^\s+/).result(' ')) + .or(any) + ) + ) + .then(function (ctrlSeq): Parser { + // TODO - is Parser correct? var cmdKlass = (LatexCmds as LatexCmdsSingleChar)[ctrlSeq]; if (cmdKlass) { @@ -51,40 +60,37 @@ var latexMathParser = (function() { var actualClass = cmdKlass as typeof TempSingleCharNode; // TODO - figure out how to know the difference return new actualClass(ctrlSeq).parser(); } else { - var builder = cmdKlass as (c:string) => TempSingleCharNode; // TODO - figure out how to know the difference + var builder = cmdKlass as (c: string) => TempSingleCharNode; // TODO - figure out how to know the difference return builder(ctrlSeq).parser(); } + } else { + return fail('unknown command: \\' + ctrlSeq); } - else { - return fail('unknown command: \\'+ctrlSeq); - } - }) - ; - - var command = - controlSequence - .or(variable) - .or(number) - .or(symbol) - ; - + }); + var command = controlSequence.or(variable).or(number).or(symbol); // Parsers yielding MathBlocks - var mathGroup:Parser= string('{').then(function() { return mathSequence; }).skip(string('}')); + var mathGroup: Parser = string('{') + .then(function () { + return mathSequence; + }) + .skip(string('}')); var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock))); var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace); - var optMathBlock = - string('[').then( - mathBlock.then(function(block) { - return block.join('latex') !== ']' ? succeed(block) : fail(''); - }) - .many().map(joinBlocks).skip(optWhitespace) - ).skip(string(']')) - ; - - var latexMath:typeof mathSequence & { + var optMathBlock = string('[') + .then( + mathBlock + .then(function (block) { + return block.join('latex') !== ']' ? succeed(block) : fail(''); + }) + .many() + .map(joinBlocks) + .skip(optWhitespace) + ) + .skip(string(']')); + var latexMath: typeof mathSequence & { block: typeof mathBlock; - optBlock: typeof optMathBlock + optBlock: typeof optMathBlock; } = mathSequence as any; latexMath.block = mathBlock; @@ -92,27 +98,26 @@ var latexMathParser = (function() { return latexMath; })(); - -optionProcessors.maxDepth = function(depth:number) { - return (typeof depth === 'number') ? depth : undefined; +optionProcessors.maxDepth = function (depth: number) { + return typeof depth === 'number' ? depth : undefined; }; class Controller_latex extends Controller_keystroke { - cleanLatex (latex:string) { + cleanLatex(latex: string) { //prune unnecessary spaces - return latex.replace(/(\\[a-z]+) (?![a-z])/ig,'$1') + return latex.replace(/(\\[a-z]+) (?![a-z])/gi, '$1'); } - exportLatex () { + exportLatex() { return this.cleanLatex(this.root.latex()); - }; - writeLatex (latex:string) { + } + writeLatex(latex: string) { var cursor = this.notify('edit').cursor; cursor.parent.writeLatex(cursor, latex); return this; - }; + } - classifyLatexForEfficientUpdate (latex:string) { + classifyLatexForEfficientUpdate(latex: string) { if (typeof latex !== 'string') return; var matches = latex.match(/-?[0-9.]+$/g); @@ -120,13 +125,13 @@ class Controller_latex extends Controller_keystroke { return { latex: latex, prefix: latex.substr(0, latex.length - matches[0].length), - digits: matches[0] + digits: matches[0], }; } return; - }; - renderLatexMathEfficiently (latex:string) { + } + renderLatexMathEfficiently(latex: string) { var root = this.root; var oldLatex = this.exportLatex(); if (root.ends[L] && root.ends[R] && oldLatex === latex) { @@ -136,14 +141,16 @@ class Controller_latex extends Controller_keystroke { var classification = this.classifyLatexForEfficientUpdate(latex); if (classification) { oldClassification = this.classifyLatexForEfficientUpdate(oldLatex); - if (!oldClassification || oldClassification.prefix !== classification.prefix) { + if ( + !oldClassification || + oldClassification.prefix !== classification.prefix + ) { return false; } } else { return false; } - // check if minus sign is changing var oldDigits = oldClassification.digits; var newDigits = classification.digits; @@ -161,7 +168,7 @@ class Controller_latex extends Controller_keystroke { // start at the very end var charNode = this.root.ends[R]; var oldCharNodes = []; - for (var i= oldDigits.length - 1; i >= 0; i--) { + for (var i = oldDigits.length - 1; i >= 0; i--) { // the tree does not match what we expect if (!charNode || charNode.ctrlSeq !== oldDigits[i]) { return false; @@ -222,7 +229,7 @@ class Controller_latex extends Controller_keystroke { // update the text of the current nodes var commonLength = Math.min(oldDigits.length, newDigits.length); - for (i=0; i < commonLength; i++) { + for (i = 0; i < commonLength; i++) { var newText = newDigits[i]; charNode = oldCharNodes[i]; if (charNode.ctrlSeq !== newText) { @@ -249,7 +256,7 @@ class Controller_latex extends Controller_keystroke { for (i = commonLength; i < newDigits.length; i++) { var span = document.createElement('span'); - span.className = "mq-digit"; + span.className = 'mq-digit'; span.textContent = newDigits[i]; var newNode = new Digit(newDigits[i]); @@ -271,7 +278,12 @@ class Controller_latex extends Controller_keystroke { var currentLatex = this.exportLatex(); if (currentLatex !== latex) { - console.warn('tried updating latex efficiently but did not work. Attempted: ' + latex + ' but wrote: ' + currentLatex); + console.warn( + 'tried updating latex efficiently but did not work. Attempted: ' + + latex + + ' but wrote: ' + + currentLatex + ); return false; } @@ -283,13 +295,17 @@ class Controller_latex extends Controller_keystroke { } return true; - }; - renderLatexMathFromScratch (latex:string) { - var root = this.root, cursor = this.cursor; + } + renderLatexMathFromScratch(latex: string) { + var root = this.root, + cursor = this.cursor; var all = Parser.all; var eof = Parser.eof; - var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex); + var block = latexMathParser + .skip(eof) + .or(all.result(false)) + .parse(latex); root.ends[L] = root.ends[R] = 0; @@ -310,15 +326,16 @@ class Controller_latex extends Controller_keystroke { this.updateMathspeak(); delete cursor.selection; cursor.insAtRightEnd(root); - }; - renderLatexMath (latex:string) { + } + renderLatexMath(latex: string) { this.notify('replace'); if (this.renderLatexMathEfficiently(latex)) return; this.renderLatexMathFromScratch(latex); - }; - renderLatexText (latex:string) { - var root = this.root, cursor = this.cursor; + } + renderLatexText(latex: string) { + var root = this.root, + cursor = this.cursor; root.jQ.children().slice(1).remove(); root.ends[L] = root.ends[R] = 0; @@ -331,12 +348,13 @@ class Controller_latex extends Controller_keystroke { var all = Parser.all; // Parser RootMathCommand - var mathMode = string('$').then(latexMathParser) + var mathMode = string('$') + .then(latexMathParser) // because TeX is insane, math mode doesn't necessarily // have to end. So we allow for the case that math mode // continues to the end of the stream. .skip(string('$').or(eof)) - .map(function(block) { + .map(function (block) { // HACK FIXME: this shouldn't have to have access to cursor var rootMathCommand = new RootMathCommand(cursor); @@ -345,13 +363,16 @@ class Controller_latex extends Controller_keystroke { block.children().adopt(rootMathBlock as MQNode, 0, 0); return rootMathCommand; - }) - ; - + }); var escapedDollar = string('\\$').result('$'); - var textChar = escapedDollar.or(regex(/^[^$]/)).map((ch) => new VanillaSymbol(ch)); + var textChar = escapedDollar + .or(regex(/^[^$]/)) + .map((ch) => new VanillaSymbol(ch)); var latexText = mathMode.or(textChar).many(); - var commands = latexText.skip(eof).or(all.result(false)).parse(latex); + var commands = latexText + .skip(eof) + .or(all.result(false)) + .parse(latex); if (commands) { for (var i = 0; i < commands.length; i += 1) { @@ -362,5 +383,5 @@ class Controller_latex extends Controller_keystroke { root.finalizeInsert(cursor.options, cursor); } - }; -}; + } +} diff --git a/src/services/mouse.ts b/src/services/mouse.ts index 5a9d84f99..1c0aa80e1 100644 --- a/src/services/mouse.ts +++ b/src/services/mouse.ts @@ -1,20 +1,24 @@ /******************************************************** * Deals with mouse events for clicking, drag-to-select *******************************************************/ -const ignoreNextMouseDownNoop = (_el:MouseEvent) => { return false }; +const ignoreNextMouseDownNoop = (_el: MouseEvent) => { + return false; +}; Options.prototype.ignoreNextMousedown = ignoreNextMouseDownNoop; // Whenever edits to the tree occur, in-progress selection events // must be invalidated and selection changes must not be applied to // the edited tree. cancelSelectionOnEdit takes care of this. -var cancelSelectionOnEdit: undefined | { - cb: () => void, - cursor: Cursor -}; +var cancelSelectionOnEdit: + | undefined + | { + cb: () => void; + cursor: Cursor; + }; (function () { ControllerBase.onNotify(function (cursor, e) { - if ((e === 'edit' || e === 'replace')) { + if (e === 'edit' || e === 'replace') { // this will be called any time ANY mathquill is edited. We only want // to cancel selection if the selection is happening within the mathquill // that dispatched the notify. Otherwise you won't be able to select any @@ -27,14 +31,17 @@ var cancelSelectionOnEdit: undefined | { })(); class Controller_mouse extends Controller_latex { - delegateMouseEvents () { + delegateMouseEvents() { var ultimateRootjQ = this.root.jQ; //drag-to-select event handling - this.container.bind('mousedown.mathquill', function(_e:Event) { + this.container.bind('mousedown.mathquill', function (_e: Event) { var e = _e as MouseEvent; var rootjQ = $(e.target).closest('.mq-root-block'); - var root = (NodeBase.getNodeOfElement(rootjQ[0]) || NodeBase.getNodeOfElement(ultimateRootjQ[0])) as ControllerRoot; - var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink; + var root = (NodeBase.getNodeOfElement(rootjQ[0]) || + NodeBase.getNodeOfElement(ultimateRootjQ[0])) as ControllerRoot; + var ctrlr = root.controller, + cursor = ctrlr.cursor, + blink = cursor.blink; var textareaSpan = ctrlr.getTextareaSpanOrThrow(); var textarea = ctrlr.getTextareaOrThrow(); @@ -44,37 +51,44 @@ class Controller_mouse extends Controller_latex { if (cursor.options.ignoreNextMousedown(e)) return; else cursor.options.ignoreNextMousedown = ignoreNextMouseDownNoop; - var target:$ | undefined; - function mousemove(e:Event) { target = $(e.target); } - function docmousemove(e:MouseEvent) { + var target: $ | undefined; + function mousemove(e: Event) { + target = $(e.target); + } + function docmousemove(e: MouseEvent) { if (!cursor.anticursor) cursor.startSelection(); ctrlr.seek(target!, e.pageX, e.pageY).cursor.select(); - if(cursor.selection) cursor.controller.aria.clear().queue(cursor.selection.join('mathspeak') + ' selected').alert(); + if (cursor.selection) + cursor.controller.aria + .clear() + .queue(cursor.selection.join('mathspeak') + ' selected') + .alert(); target = undefined; } // outside rootjQ, the MathQuill node corresponding to the target (if any) // won't be inside this root, so don't mislead Controller::seek with it - function unbindListeners (e:MouseEvent) { + function unbindListeners(e: MouseEvent) { // delete the mouse handlers now that we're not dragging anymore rootjQ.unbind('mousemove', mousemove); - const anyTarget = e.target as any; // TODO - why do we need to cast to any? - $(anyTarget.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup); + const anyTarget = e.target as any; // TODO - why do we need to cast to any? + $(anyTarget.ownerDocument) + .unbind('mousemove', docmousemove) + .unbind('mouseup', mouseup); cancelSelectionOnEdit = undefined; } - function updateCursor () { + function updateCursor() { if (ctrlr.editable) { cursor.show(); cursor.controller.aria.queue(cursor.parent).alert(); - } - else { + } else { textareaSpan.detach(); } } - function mouseup(e:MouseEvent) { + function mouseup(e: MouseEvent) { cursor.blink = blink; if (!cursor.selection) updateCursor(); unbindListeners(e); @@ -92,8 +106,8 @@ class Controller_mouse extends Controller_latex { cursor.clearSelection(); updateCursor(); unbindListeners(e); - } - } + }, + }; if (ctrlr.blurred) { if (!ctrlr.editable) rootjQ.prepend(textareaSpan); @@ -109,17 +123,17 @@ class Controller_mouse extends Controller_latex { ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection(); rootjQ.mousemove(mousemove); - const anyTarget = e.target as any; // TODO - why do we need to cast to any? + const anyTarget = e.target as any; // TODO - why do we need to cast to any? $(anyTarget.ownerDocument).mousemove(docmousemove).mouseup(mouseup); // listen on document not just body to not only hear about mousemove and // mouseup on page outside field, but even outside page, except iframes: https://github.com/mathquill/mathquill/commit/8c50028afcffcace655d8ae2049f6e02482346c5#commitcomment-6175800 }); } - - seek ($target:$, pageX:number, _pageY:number) { + + seek($target: $, pageX: number, _pageY: number) { var cursor = this.notify('select').cursor; var node; - var targetElm:HTMLElement | null = $target && $target[0]; + var targetElm: HTMLElement | null = $target && $target[0]; // we can click on an element that is deeply nested past the point // that mathquill knows about. We need to traverse up to the first @@ -145,7 +159,7 @@ class Controller_mouse extends Controller_latex { node.seek(pageX, cursor); this.scrollHoriz(); // before .selectFrom when mouse-selecting, so - // always hits no-selection case in scrollHoriz and scrolls slower + // always hits no-selection case in scrollHoriz and scrolls slower return this; - }; -}; + } +} diff --git a/src/services/parser.util.ts b/src/services/parser.util.ts index f99f2e846..d380fb008 100644 --- a/src/services/parser.util.ts +++ b/src/services/parser.util.ts @@ -1,13 +1,11 @@ - function parseError(stream: string, message: string): never { if (stream) { - stream = "'"+stream+"'"; - } - else { + stream = "'" + stream + "'"; + } else { stream = 'EOF'; } - throw 'Parse Error: '+message+' at '+stream; + throw 'Parse Error: ' + message + ' at ' + stream; } type UnknownParserResult = any; @@ -18,7 +16,6 @@ type ParserBody = ( onFailure: (stream: string, msg: string) => UnknownParserResult ) => T; - class Parser { _: ParserBody; @@ -28,50 +25,52 @@ class Parser { // You should never call the constructor, rather you should // construct your Parser from the base parsers and the // parser combinator methods. - constructor (body: ParserBody) { + constructor(body: ParserBody) { this._ = body; } - parse (stream: string): T { - return this.skip(Parser.eof)._(''+stream, success, parseError); + parse(stream: string): T { + return this.skip(Parser.eof)._('' + stream, success, parseError); - function success(_stream: string, result: T) { return result; } - }; + function success(_stream: string, result: T) { + return result; + } + } // -*- primitive combinators -*- // - or (alternative: Parser): Parser { + or(alternative: Parser): Parser { pray('or is passed a parser', alternative instanceof Parser); var self = this; - return new Parser(function(stream, onSuccess, onFailure) { + return new Parser(function (stream, onSuccess, onFailure) { return self._(stream, onSuccess, failure); function failure(_newStream: string) { return alternative._(stream, onSuccess, onFailure); } }); - }; + } - then (next:Parser|((result: T)=>Parser)):Parser { + then(next: Parser | ((result: T) => Parser)): Parser { var self = this; - return new Parser(function(stream: string, onSuccess, onFailure) { + return new Parser(function (stream: string, onSuccess, onFailure) { return self._(stream, success, onFailure) as any as Q; function success(newStream: string, result: T) { - var nextParser = (next instanceof Parser ? next : next(result)); + var nextParser = next instanceof Parser ? next : next(result); pray('a parser is returned', nextParser instanceof Parser); return nextParser._(newStream, onSuccess, onFailure); } }); - }; + } // -*- optimized iterative combinators -*- // - many (): Parser { + many(): Parser { var self = this; - return new Parser(function(stream, onSuccess, _onFailure) { + return new Parser(function (stream, onSuccess, _onFailure) { var xs: T[] = []; while (self._(stream, success, failure)); return onSuccess(stream, xs); @@ -86,13 +85,13 @@ class Parser { return false; } }); - }; + } - times (min: number, max?: number): Parser { + times(min: number, max?: number): Parser { if (arguments.length < 2) max = min; var self = this; - return new Parser(function(stream, onSuccess, onFailure) { + return new Parser(function (stream, onSuccess, onFailure) { var xs: T[] = []; var result: boolean = true; var failure; @@ -126,74 +125,80 @@ class Parser { return false; } }); - }; + } // -*- higher-level combinators -*- // - result (res: Q): Parser { return this.then(Parser.succeed(res)); }; - atMost (n: number) { return this.times(0, n); }; - atLeast (n: number) { + result(res: Q): Parser { + return this.then(Parser.succeed(res)); + } + atMost(n: number) { + return this.times(0, n); + } + atLeast(n: number) { var self = this; - return self.times(n).then(function(start) { - return self.many().map(function(end) { + return self.times(n).then(function (start) { + return self.many().map(function (end) { return start.concat(end); }); }); - }; + } - map (fn: (result: T)=>Q): Parser { - return this.then(function(result) { return Parser.succeed(fn(result)); }); - }; + map(fn: (result: T) => Q): Parser { + return this.then(function (result) { + return Parser.succeed(fn(result)); + }); + } - skip (two: Parser): Parser { - return this.then(function(result) { return two.result(result); }); - }; + skip(two: Parser): Parser { + return this.then(function (result) { + return two.result(result); + }); + } // -*- primitive parsers -*- // - static string (str: string): Parser { + static string(str: string): Parser { var len = str.length; - var expected = "expected '"+str+"'"; + var expected = "expected '" + str + "'"; - return new Parser(function(stream, onSuccess, onFailure) { + return new Parser(function (stream, onSuccess, onFailure) { var head = stream.slice(0, len); if (head === str) { return onSuccess(stream.slice(len), head); - } - else { + } else { return onFailure(stream, expected); } }); - }; + } - static regex (re: RegExp): Parser { + static regex(re: RegExp): Parser { pray('regexp parser is anchored', re.toString().charAt(1) === '^'); - var expected = 'expected '+re; + var expected = 'expected ' + re; - return new Parser(function(stream, onSuccess, onFailure) { + return new Parser(function (stream, onSuccess, onFailure) { var match = re.exec(stream); if (match) { var result = match[0]; return onSuccess(stream.slice(result.length), result); - } - else { + } else { return onFailure(stream, expected); } }); - }; + } - static succeed (result: Q): Parser { - return new Parser(function(stream: string, onSuccess) { + static succeed(result: Q): Parser { + return new Parser(function (stream: string, onSuccess) { return onSuccess(stream, result); }); - }; + } - static fail (msg: string):Parser { - return new Parser(function(stream, _, onFailure) { + static fail(msg: string): Parser { + return new Parser(function (stream, _, onFailure) { return onFailure(stream, msg) as never; }); - }; + } static letter = Parser.regex(/^[a-z]/i); static letters = Parser.regex(/^[a-z]*/i); @@ -202,19 +207,31 @@ class Parser { static whitespace = Parser.regex(/^\s+/); static optWhitespace = Parser.regex(/^\s*/); - static any: Parser = new Parser(function(stream, onSuccess, onFailure) { + static any: Parser = new Parser(function ( + stream, + onSuccess, + onFailure + ) { if (!stream) return onFailure(stream, 'expected any character'); return onSuccess(stream.slice(1), stream.charAt(0)); }); - static all: Parser = new Parser(function(stream, onSuccess, _onFailure) { + static all: Parser = new Parser(function ( + stream, + onSuccess, + _onFailure + ) { return onSuccess('', stream); }); - static eof: Parser = new Parser(function(stream, onSuccess, onFailure) { + static eof: Parser = new Parser(function ( + stream, + onSuccess, + onFailure + ) { if (stream) return onFailure(stream, 'expected EOF'); return onSuccess(stream, stream); }); -}; +} diff --git a/src/services/saneKeyboardEvents.util.ts b/src/services/saneKeyboardEvents.util.ts index 32ed871c1..96c936b06 100644 --- a/src/services/saneKeyboardEvents.util.ts +++ b/src/services/saneKeyboardEvents.util.ts @@ -20,9 +20,9 @@ * + event handler logic * + attach event handlers and export methods ************************************************/ -type TextareaChecker = ((e?:Event) => void); +type TextareaChecker = (e?: Event) => void; -var saneKeyboardEvents = (function() { +var saneKeyboardEvents = (function () { // The following [key values][1] map was compiled from the // [DOM3 Events appendix section on key codes][2] and // [a widely cited report on cross-browser tests of key codes][3], @@ -32,7 +32,7 @@ var saneKeyboardEvents = (function() { // [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues // [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes // [3]: http://unixpapa.com/js/key.html - const KEY_VALUES:Record = { + const KEY_VALUES: Record = { 8: 'Backspace', 9: 'Tab', @@ -63,12 +63,12 @@ var saneKeyboardEvents = (function() { 46: 'Del', - 144: 'NumLock' + 144: 'NumLock', }; // To the extent possible, create a normalized string representation // of the key combo (i.e., key code and modifier keys). - function stringify(evt:JQ_KeyboardEvent) { + function stringify(evt: JQ_KeyboardEvent) { var which = evt.which || evt.keyCode; var keyVal = KEY_VALUES[which]; var key; @@ -89,9 +89,9 @@ var saneKeyboardEvents = (function() { // create a keyboard events shim that calls callbacks at useful times // and exports useful public methods - return function saneKeyboardEvents(el:$, controller:Controller) { - var keydown:JQ_KeyboardEvent | null = null; - var keypress:KeyboardEvent | null = null; + return function saneKeyboardEvents(el: $, controller: Controller) { + var keydown: JQ_KeyboardEvent | null = null; + var keypress: KeyboardEvent | null = null; var textarea = jQuery(el); var target = jQuery(controller.container || textarea); @@ -104,25 +104,28 @@ var saneKeyboardEvents = (function() { // after selecting something and then typing, the textarea is // incorrectly reported as selected during the input event (but not // subsequently). - var checkTextarea:TextareaChecker = noop; - var timeoutId:number; - function checkTextareaFor(checker:TextareaChecker) { + var checkTextarea: TextareaChecker = noop; + var timeoutId: number; + function checkTextareaFor(checker: TextareaChecker) { checkTextarea = checker; clearTimeout(timeoutId); timeoutId = setTimeout(checker); } - function checkTextareaOnce(checker:TextareaChecker) { - checkTextareaFor(function(e) { + function checkTextareaOnce(checker: TextareaChecker) { + checkTextareaFor(function (e) { checkTextarea = noop; clearTimeout(timeoutId); checker(e); }); } - target.bind('keydown keypress input keyup paste', function(e:KeyboardEvent) { - checkTextarea(e); - }); + target.bind( + 'keydown keypress input keyup paste', + function (e: KeyboardEvent) { + checkTextarea(e); + } + ); - function guardedTextareaSelect () { + function guardedTextareaSelect() { try { // IE can throw an 'Incorrect Function' error if you // try to select a textarea that is hidden. It seems @@ -130,11 +133,11 @@ var saneKeyboardEvents = (function() { // fails to happen in this case. Why would the textarea // be hidden? And who would even be able to tell? textarea[0].select(); - } catch (e) {}; + } catch (e) {} } // -*- public methods -*- // - function select(text:string) { + function select(text: string) { // check textarea at least once/one last time before munging (so // no race condition if selection happens after keypress/paste but // before checkTextarea), then never again ('cos it's been munged) @@ -169,24 +172,25 @@ var saneKeyboardEvents = (function() { } // -*- event handlers -*- // - function onKeydown(e:KeyboardEvent) { + function onKeydown(e: KeyboardEvent) { if (e.target !== textarea[0]) return; keydown = e; keypress = null; - if (shouldBeSelected) checkTextareaOnce(function(e?:Event) { - if (!(e && e.type === 'focusout')) { - // re-select textarea in case it's an unrecognized key that clears - // the selection, then never again, 'cos next thing might be blur - guardedTextareaSelect() - } - }); + if (shouldBeSelected) + checkTextareaOnce(function (e?: Event) { + if (!(e && e.type === 'focusout')) { + // re-select textarea in case it's an unrecognized key that clears + // the selection, then never again, 'cos next thing might be blur + guardedTextareaSelect(); + } + }); handleKey(); } - function isArrowKey (e:JQ_KeyboardEvent) { + function isArrowKey(e: JQ_KeyboardEvent) { if (!e || !e.originalEvent) return false; // The keyPress event in FF reports which=0 for some reason. The new @@ -201,7 +205,7 @@ var saneKeyboardEvents = (function() { return false; } - function onKeypress(e:KeyboardEvent) { + function onKeypress(e: KeyboardEvent) { if (e.target !== textarea[0]) return; // call the key handler for repeated keypresses. @@ -220,12 +224,11 @@ var saneKeyboardEvents = (function() { checkTextareaFor(typedText); } } - function onKeyup(e:KeyboardEvent) { + function onKeyup(e: KeyboardEvent) { if (e.target !== textarea[0]) return; // Handle case of no keypress event being sent if (!!keydown && !keypress) { - // only check for typed text if this key can type text. Otherwise // you can end up with mathquill thinking text was typed if you // use the mq.keystroke('Right') command while a single character @@ -276,7 +279,7 @@ var saneKeyboardEvents = (function() { textarea.val(''); } - function onPaste(e:Event) { + function onPaste(e: Event) { if (e.target !== textarea[0]) return; // browsers are dumb. @@ -311,9 +314,15 @@ var saneKeyboardEvents = (function() { keypress: onKeypress, keyup: onKeyup, focusout: onBlur, - copy: function(e:Event) { e.preventDefault(); }, - cut: function(e:Event) { e.preventDefault(); }, - paste: function(e:Event) { e.preventDefault(); } + copy: function (e: Event) { + e.preventDefault(); + }, + cut: function (e: Event) { + e.preventDefault(); + }, + paste: function (e: Event) { + e.preventDefault(); + }, }); } else { target.bind({ @@ -321,15 +330,23 @@ var saneKeyboardEvents = (function() { keypress: onKeypress, keyup: onKeyup, focusout: onBlur, - cut: function() { checkTextareaOnce(function() { controller.cut(); }); }, - copy: function() { checkTextareaOnce(function() { controller.copy(); }); }, - paste: onPaste + cut: function () { + checkTextareaOnce(function () { + controller.cut(); + }); + }, + copy: function () { + checkTextareaOnce(function () { + controller.copy(); + }); + }, + paste: onPaste, }); } // -*- export public methods -*- // return { - select: select + select: select, }; }; -}()); +})(); diff --git a/src/services/scrollHoriz.ts b/src/services/scrollHoriz.ts index 3075a402b..6967a3ea6 100644 --- a/src/services/scrollHoriz.ts +++ b/src/services/scrollHoriz.ts @@ -4,7 +4,7 @@ **********************************************/ class Controller_scrollHoriz extends Controller_mouse { - setOverflowClasses () { + setOverflowClasses() { var root = this.root.jQ[0]; var shouldHaveOverflowRight = false; var shouldHaveOverflowLeft = false; @@ -12,19 +12,26 @@ class Controller_scrollHoriz extends Controller_mouse { var width = root.getBoundingClientRect().width; var scrollWidth = root.scrollWidth; var scroll = root.scrollLeft; - shouldHaveOverflowRight = (scrollWidth > width + scroll); - shouldHaveOverflowLeft = (scroll > 0); + shouldHaveOverflowRight = scrollWidth > width + scroll; + shouldHaveOverflowLeft = scroll > 0; } - if (root.classList.contains('mq-editing-overflow-right') !== shouldHaveOverflowRight) - root.classList.toggle('mq-editing-overflow-right') - if (root.classList.contains('mq-editing-overflow-left') !== shouldHaveOverflowLeft) - root.classList.toggle('mq-editing-overflow-left') + if ( + root.classList.contains('mq-editing-overflow-right') !== + shouldHaveOverflowRight + ) + root.classList.toggle('mq-editing-overflow-right'); + if ( + root.classList.contains('mq-editing-overflow-left') !== + shouldHaveOverflowLeft + ) + root.classList.toggle('mq-editing-overflow-left'); } - scrollHoriz () { - var cursor = this.cursor, seln = cursor.selection; + scrollHoriz() { + var cursor = this.cursor, + seln = cursor.selection; var rootRect = this.root.jQ[0].getBoundingClientRect(); if (!cursor.jQ[0] && !seln) { - this.root.jQ.stop().animate({scrollLeft: 0}, 100, () => { + this.root.jQ.stop().animate({ scrollLeft: 0 }, 100, () => { this.setOverflowClasses(); }); return; @@ -40,26 +47,26 @@ class Controller_scrollHoriz extends Controller_mouse { if (seln.ends[L] === cursor[R]) { if (overLeft < 0) var scrollBy = overLeft; else if (overRight > 0) { - if (rect.left - overRight < rootRect.left + 20) var scrollBy = overLeft; + if (rect.left - overRight < rootRect.left + 20) + var scrollBy = overLeft; else var scrollBy = overRight; - } - else return; - } - else { + } else return; + } else { if (overRight > 0) var scrollBy = overRight; else if (overLeft < 0) { - if (rect.right - overLeft > rootRect.right - 20) var scrollBy = overRight; + if (rect.right - overLeft > rootRect.right - 20) + var scrollBy = overRight; else var scrollBy = overLeft; - } - else return; + } else return; } } - var root = this.root.jQ[0] - if (scrollBy < 0 && root.scrollLeft === 0) return - if (scrollBy > 0 && root.scrollWidth <= root.scrollLeft + rootRect.width) return - this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy}, 100, () => { + var root = this.root.jQ[0]; + if (scrollBy < 0 && root.scrollLeft === 0) return; + if (scrollBy > 0 && root.scrollWidth <= root.scrollLeft + rootRect.width) + return; + this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy }, 100, () => { this.setOverflowClasses(); }); - }; -}; + } +} diff --git a/src/services/textarea.ts b/src/services/textarea.ts index aa23be57a..cac0b0201 100644 --- a/src/services/textarea.ts +++ b/src/services/textarea.ts @@ -2,17 +2,21 @@ * Manage the MathQuill instance's textarea * (as owned by the Controller) ********************************************/ -Options.prototype.substituteTextarea = function() { - return $(' - - - - - - + + + + + MathQuill-basic Demo + + + + + +
+ Fork me on GitHub! + +

+ MathQuill-basic Demo + local test page +

+ +

+ Backslash \ and dollar $ aren't special, + / still works, font works: +

+ +

\sqrt{2}

+ +

+
+ + + + + diff --git a/test/demo.html b/test/demo.html index 43be9a04d..18a08c335 100644 --- a/test/demo.html +++ b/test/demo.html @@ -1,166 +1,279 @@ - - - - - - - -MathQuill Demo - - - - - - - - -
- -Fork me on GitHub! - -

MathQuill Demo local test page

- -

Math textbox with initial LaTeX: \frac{d}{dx}\sqrt{x}=

- -

Try typing 1/2\sqrt x and using the arrow keys to move around. Shortcut: use the tab key instead of arrow keys to get "out" of a command, like a_n[tab]x^n. Many LaTeX-style symbols and commands preceded by a backslash are supported, such as \forall or n\choose k.

- -

Latex source:

- -

LaTeX rendered as an image Link

- -

Show Semantically Meaningful HTML Source

-

-
-

You could actually just copy-and-paste this HTML into any element with class="mq-math-mode" on a page that includes the mathquill.css and it would render beautifully, like this:

- -

If you simply want to display some non-interactive math, you can use MathQuill's StaticMath API: e^{i\pi}+1=0. If you select and copy static math, by default it will copy LaTeX source to the clipboard.

- -

You can also make static math non-selectable: sin^2\theta + cos^2\theta = 1.

- -

Note that if you're only rendering static math, MathJax supports more of LaTeX and renders better.

- -

In many applications, such as a chat client, you probably type mostly normal text with some math interspersed, so there is also a MathQuill textbox that let's you type math between $'s: The Quadratic Equation is $x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$

- -

LaTeX math can also have textboxes inside: \int\MathQuillMathField{}dx or even \sqrt{\MathQuillMathField{x^2+y^2}}

- -

This button runs the JavaScript code written on it to MathQuill-ify the following <span> element into an editable math textbox: \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}}

- -
- - - - - + + + + + + + MathQuill Demo + + + + + + + +
+ Fork me on GitHub! + +

+ MathQuill Demo + local test page +

+ +

+ Math textbox with initial LaTeX: + \frac{d}{dx}\sqrt{x}= +

+ +

+ Try typing 1/2\sqrt x and using the arrow keys to move + around. Shortcut: use the tab key instead of arrow keys to get + "out" of a command, like + a_n[tab]x^n. Many LaTeX-style symbols and + commands preceded by a backslash are supported, such as + \forall or n\choose k. +

+ +

+ Latex source: + +

+ +

+ + LaTeX rendered as an image + Link +

+ +

+ Show Semantically Meaningful HTML Source +

+

+
+      

+ You could actually just copy-and-paste this HTML into any element with + class="mq-math-mode" on a page that includes the + mathquill.css and it would render beautifully, like this: + +

+ +

+ If you simply want to display some non-interactive math, you can use + MathQuill's StaticMath API: + e^{i\pi}+1=0. If you select + and copy static math, by default it will copy LaTeX source to the + clipboard. +

+ +

+ You can also make static math non-selectable: + sin^2\theta + cos^2\theta = 1. +

+ +

+ Note that if you're only rendering static math, + MathJax supports more of LaTeX and + renders better. +

+ +

+ In many applications, such as a chat client, you probably type mostly + normal text with some math interspersed, so there is also a MathQuill + textbox that let's you type math between $'s: + The Quadratic Equation is $x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$ +

+ +

+ LaTeX math can also have textboxes inside: + \int\MathQuillMathField{}dx + or even + \sqrt{\MathQuillMathField{x^2+y^2}} +

+ +

+ This button runs the JavaScript code written on it to MathQuill-ify the + following <span> element into an editable math + textbox: + \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = + \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} +

+
+ + + + + diff --git a/test/digit-grouping.html b/test/digit-grouping.html index 276879645..008807859 100644 --- a/test/digit-grouping.html +++ b/test/digit-grouping.html @@ -1,86 +1,126 @@ - - - - - -MathQuill Digit Grouping Demo - - - - - - -
- -Fork me on GitHub! - -

MathQuill Digit Grouping Demo

- - -

Grouping Disabled

-

-12345678 + .23232323 + 23232.23233 - -

Grouping Enabled

-

-12345678 + .23232323 + 23232.23233 - -

Optimized latex updates

-

-12345678.2342342 - -

Edge Cases

-

- -

- - - - + + + + + MathQuill Digit Grouping Demo + + + + + +
+ Fork me on GitHub! + +

+ MathQuill Digit Grouping Demo +

+ +

Grouping Disabled

+

+ -12345678 + .23232323 + 23232.23233 +

+ +

Grouping Enabled

+

+ -12345678 + .23232323 + 23232.23233 +

+ +

Optimized latex updates

+

-12345678.2342342

+ +

Edge Cases

+

+
+ + + + + diff --git a/test/support/assert.js b/test/support/assert.js index 0b8ae8a52..a651fccaa 100644 --- a/test/support/assert.js +++ b/test/support/assert.js @@ -1,4 +1,4 @@ -window.assert = (function() { +window.assert = (function () { function AssertionError(opts) { if (!opts) opts = {}; @@ -19,28 +19,28 @@ window.assert = (function() { } return { - ok: function(thing, message) { + ok: function (thing, message) { if (thing) return; fail({ message: message, - explanation: 'expected '+thing+' to be truthy' + explanation: 'expected ' + thing + ' to be truthy', }); }, - equal: function(thing1, thing2, message) { + equal: function (thing1, thing2, message) { if (thing1 === thing2) return; fail({ message: message, - explanation: 'expected ('+thing1+') to equal ('+thing2+')' + explanation: 'expected (' + thing1 + ') to equal (' + thing2 + ')', }); }, - throws: function(fn, message) { + throws: function (fn, message) { var error = false; try { fn(); - } catch(e) { + } catch (e) { error = true; } @@ -48,11 +48,11 @@ window.assert = (function() { fail({ message: message, - explanation: 'expected '+fn+' to throw an error' + explanation: 'expected ' + fn + ' to throw an error', }); }, - fail: function(message) { + fail: function (message) { fail({ message: message, explanation: 'generic fail' }); - } - } + }, + }; })(); diff --git a/test/support/home.css b/test/support/home.css index c4db5e47b..c06f56ef5 100644 --- a/test/support/home.css +++ b/test/support/home.css @@ -1,5 +1,5 @@ html { - background-color: #DDF; + background-color: #ddf; height: 100%; } body { @@ -25,14 +25,14 @@ h1 small { } code { font: 90% monospace; - background: #DDD; - border-radius: .3em; - -moz-border-radius: .3em; - -webkit-border-radius: .3em; - padding: 0 .3em; + background: #ddd; + border-radius: 0.3em; + -moz-border-radius: 0.3em; + -webkit-border-radius: 0.3em; + padding: 0 0.3em; } code:hover { - color: #DDD; + color: #ddd; background: #222; } a { @@ -41,11 +41,11 @@ a { } a:hover { color: black; - text-shadow: #40F 0 0 .2em; + text-shadow: #40f 0 0 0.2em; } a:active { color: #400; - text-shadow: red 0 0 .2em; + text-shadow: red 0 0 0.2em; } ul { padding-left: 1em; diff --git a/test/unit.html b/test/unit.html index ec8ba4946..a7f2ee885 100644 --- a/test/unit.html +++ b/test/unit.html @@ -3,40 +3,52 @@ - + - - + + - + @@ -45,7 +57,6 @@ MQBasic = MathQuill.noConflict().getInterface(MathQuill.getInterface.MAX); MQ = MathQuill.getInterface(MathQuill.getInterface.MAX); - @@ -55,16 +66,20 @@

Unit Tests

- - - - - -
- -Fork me on GitHub! - -

MathQuill Tests local test page

- - - -

MathQuill Editables

- -

In all editable fields, the selection should clear and ghost parens should solidify if you click outside, but not if you switch tabs/windows and switch back to this page. - - - -
Initial LaTeX -
\frac{d}{dx}\sqrt{x}=\frac{1}{2\sqrt{x}} - lolwut $a^2 + b^2 = c^2$. Also, awesomesauce: $\int_0^1 \sin x dx. - \sqrt{\MathQuillMathField{x^2+y^2}} -
- -

Touch taps/clicks/mousedown to drag should work anywhere in the blue box:

x_{very\ long\ thing}^2 + a_0 = 0
- -

Redrawing

-

- \sqrt{} - should look the same as - - \sqrt{\pi\sqrt\sqrt\frac12} - -

- - -

Behavior Options

- -

x_a^b + \frac{\sqrt[n]{x}}{\frac{1}{2}}

-

Space should behave like Tab, left and right should go through the upper block, sums should start with n=, exponents should require an base, any of +-=<> should break out of an exponent, pi, theta, sqrt, and sum should all be auto-commands, but only should be the only auto-operator name (so sin etc. shouldn't automatically become non-italicized).

- - - -

Up/Down seeking and caching

- -

- - \frac{1}{\sqrt \sqrt \sqrt \sqrt \sqrt \sqrt x} - -

- -

-↑ If you hit down from next to the 1, you should end up inside one of the square roots. If you hit up from the right of the x and then hit down again, you should end up where you were. -

- -

Horizontal overflow

- -

- - \frac{d}{dx}\sqrt{x}=\frac{d}{dx}x^{\frac{1}{2}}=\frac{1}{2}x^{-\frac{1}{2}}=\frac{1}{2\sqrt{x}} - -(for comparison: ) -

- - - -

Selection Tests

- -

lolwut $a^2 + b^2 = c^2$. $\sqrt{ \left( \frac{1}{2} \right) }$. Also, awesomesauce: $\int_0^1 \sin x dx. - -

Even in IE<9, the background color of the parens and square root radical should be the background color of the selection. - -

Static math with mouseEvents set to false should not interact with the mouse: 12 + 34

- -

Even in the case where it has an empty element: \sqrt{}

- -

Dynamic mathquill-ification

- - - -
Initial LaTeX -
\frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} - \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} - \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} -
\frac{ \text{apples} }{ \text{oranges} } = \text{NaN} - \frac{ \text{apples} }{ \text{oranges} } = \text{NaN} - \frac{ \text{apples} }{ \text{oranges} } = \text{NaN} -
- - -
MQ(...).reflow() -
\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} - \sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} - \sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} -
- -

Static LaTeX rendering (.mathquill-static-math) tests

- -
^{\frac{as}{ }df}^{\frac{as}{ }df} -
e^{i\pi}+1=0e^{i\pi}+1=0 -
\sqrt[n]{1}\sqrt[n]{1} -
\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x) -
12a\sin b12a\sin b -
1a^2 \sin b1a^2 \sin b -
a + \sin ba + \sin b -
a + ba + b -
\sum\sin\sum\sin -
\sum a\sum a -
(\sin)(\sin) -
\left(\sin\right)\left(\sin\right) -
(x)\sin(x)(x)\sin(x) -
\left(x\right)\sin\left(x\right)\left(x\right)\sin\left(x\right) -
a \sin ba \sin b -
-1 + +-2-1 + +-2 -
\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k} -
x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}}x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}} -
\left(\frac{\frac{\frac{1}{2}}{\frac{3}{4}}}{\frac{\frac{5}{6}}{\frac{7}{8}}}\right)\left(\frac{\frac{\frac{1}{2}}{\frac{3}{4}}}{\frac{\frac{5}{6}}{\frac{7}{8}}}\right) -
\left| a + \left| b \right| \right|\left| a + \left| b \right| \right| -
\sqrt{x}+\sqrt{\frac{x}{\frac{ }{\frac{ }{ }}}}+\sqrt{\frac{x}{\frac{ }{\frac{ }{\frac{ }{\frac{ }{ }}}}}}\sqrt{x}+\sqrt{\frac{x}{\frac{ }{\frac{ }{ }}}}+\sqrt{\frac{x}{\frac{ }{\frac{ }{\frac{ }{\frac{ }{ }}}}}} -
1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}^M -
x\ \ \ +\ \ \ yx\ \ \ +\ \ \ y^M -
\sum _{n=0}^3\cos x\sum _{n=0}^3\cos x^M -
\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}^M -
\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi \int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi -
\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}} -
\overline{abc}\overline{abc} -
\overleftarrow{abc}\overleftarrow{abc} -
\overrightarrow{abc}\overrightarrow{abc} -
\overleftrightarrow{abc}\overleftrightarrow{abc} -
\overarc{abc}\overarc{abc} -
- -
- -

Parentheses vertical alignment at font sizes ranging from 10px to 24px:

- - -

Dynamic rendering performance

-

LaTeX:

-

HTML:

- -

There should be no space between here and here, even in IE8

- -

Textcolor

- -

Colors should match their names: - - \textcolor{#0000FF}{blue} + \textcolor{red}{red} - = \textcolor { rgb(255, -10, 255) } {magenta} - -

-

Nested \textcolor: the 2 should be red, the "a+" green, the 4 blue, and the "+b" green again. - -e^{\textcolor{#FF0000}{2}}\sin(x^\textcolor{#00FF00}{{a+\textcolor{blue}{4}+b}}) - -

- -

Adding CSS class

- -

Second term should be styled like this: - -x+\class{testclass}{y}+z - -

- -

substituteTextarea

- -

In Safari on iOS, this should be focusable but not bring up the on-screen keyboard; to test, try focusing anything else and confirm this blurs: (confirmed working on iOS 6.1.3)

- -

overrideKeystroke and overrideTypedText

- -

Should be able to prevent typing in this field: 1+2+3

- -

Should wrap anything you type in '<>': 1+2+3

- -

Text mode

- -

Spaces at the beginning and end of text mode blocks should be visible: 1\text{ And }2

- -

Mutiple consecutive spaces in the middle of a text mode block should not collapse into one space: \text{three spaces}

-

-

    -
  • [Firefox] Should not change '3' to '33' when you select all and press right arrow.
  • -
  • [All Browsers] ctrl-c or cmd-c should not throw an error
  • - -
3 -

- -

substituteKeyboardEvents (legacy, use overrideKeystroke and overrideTypedText instead)

- -

Should be able to prevent cut, typing, and pasting in this field: 1+2+3

- -

Should wrap anything you type in '<>': 1+2+3

- -

-

    -
  • [Firefox] Should not change '3' to '33' when you select all and press right arrow.
  • -
  • [All Browsers] ctrl-c or cmd-c should not throw an error
  • - -
3 -

- -
- - - - - + + + + + + + MathQuill Test Page + + + + + + + + + +
+ Fork me on GitHub! + +

+ MathQuill Tests + local test page +

+ + + +

MathQuill Editables

+ +

+ In all editable fields, the selection should clear and ghost parens + should solidify if you click outside, but not if you switch tabs/windows + and switch back to this page. +

+ + + + + + + + + + +
+ Initial LaTeX +
+ \frac{d}{dx}\sqrt{x}=\frac{1}{2\sqrt{x}} + + lolwut $a^2 + b^2 = c^2$. Also, awesomesauce: $\int_0^1 \sin + x dx. + + \sqrt{\MathQuillMathField{x^2+y^2}} +
+ +

+ Touch taps/clicks/mousedown to drag should work anywhere in the blue + box: +

+
+ x_{very\ long\ thing}^2 + a_0 = 0 +
+ +

Redrawing

+

+ \sqrt{} + should look the same as + \sqrt{\pi\sqrt\sqrt\frac12} +

+ + +

Behavior Options

+ +

+ x_a^b + \frac{\sqrt[n]{x}}{\frac{1}{2}} +

+

+ Space should behave like Tab, left and right should go through the upper + block, sums should start with n=, exponents should require + an base, any of +-=<> should break out of an + exponent, pi, theta, sqrt, and + sum should all be auto-commands, but + only should be the only auto-operator name (so + sin etc. shouldn't automatically become non-italicized). +

+ + + +

Up/Down seeking and caching

+ +

+ + \frac{1}{\sqrt \sqrt \sqrt \sqrt \sqrt \sqrt x} + +

+ +

+ ↑ If you hit down from next to the 1, you should end up inside one + of the square roots. If you hit up from the right of the x and then hit + down again, you should end up where you were. +

+ +

Horizontal overflow

+ +

+ + \frac{d}{dx}\sqrt{x}=\frac{d}{dx}x^{\frac{1}{2}}=\frac{1}{2}x^{-\frac{1}{2}}=\frac{1}{2\sqrt{x}} + + (for comparison: + ) +

+ + + +

Selection Tests

+ +

+ lolwut $a^2 + b^2 = c^2$. $\sqrt{ \left( \frac{1}{2} \right) }$. + Also, awesomesauce: $\int_0^1 \sin x dx. +

+ +

+ Even in IE<9, the background color of the parens and square root + radical should be the background color of the selection. +

+ +

+ Static math with mouseEvents set to false should not interact with the + mouse: 12 + 34 +

+ +

+ Even in the case where it has an empty element: + \sqrt{} +

+ +

Dynamic mathquill-ification

+ + + + + + + + + + + + + + +
+ Initial LaTeX +
+ \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = + \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} + + \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = + \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} + + \frac{d}{dx}\sqrt{x} = \frac{d}{dx}x^{\frac{1}{2}} = + \frac{1}{2}x^{-\frac{1}{2}} = \frac{1}{2\sqrt{x}} +
+ \frac{ \text{apples} }{ \text{oranges} } = + \text{NaN} + + \frac{ \text{apples} }{ \text{oranges} } = + \text{NaN} + + \frac{ \text{apples} }{ \text{oranges} } = + \text{NaN} +
+ + + + + + + + + + +
+ MQ(...).reflow() +
+ \sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + + \binom{n}{k} + + \sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + + \binom{n}{k} + + \sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + + \binom{n}{k} +
+ +

+ Static LaTeX rendering (.mathquill-static-math) tests +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
^{\frac{as}{ }df} + ^{\frac{as}{ }df} +
e^{i\pi}+1=0 + e^{i\pi}+1=0 +
+ \sqrt[n]{1} + + \sqrt[n]{1} +
+ \sin ^2x+\sin ^2\left(x\right)+\sin ^2(x) + + \sin ^2x+\sin ^2\left(x\right)+\sin ^2(x) +
+ 12a\sin b + + 12a\sin b +
+ 1a^2 \sin b + + 1a^2 \sin b +
+ a + \sin b + + a + \sin b +
+ a + b + + a + b +
+ \sum\sin + + \sum\sin +
+ \sum a + + \sum a +
+ (\sin) + + (\sin) +
+ \left(\sin\right) + + \left(\sin\right) +
+ (x)\sin(x) + + (x)\sin(x) +
+ \left(x\right)\sin\left(x\right) + + \left(x\right)\sin\left(x\right) +
+ a \sin + b + + a \sin + b +
+ -1 + + +-2 + + -1 + + +-2 +
+ \left + ( + n+1 + \right + ) + + + \frac{1}{\frac{n}{k}} + + + \binom{n}{k} + + \left + ( + n+1 + \right + ) + + + \frac{1}{\frac{n}{k}} + + + \binom{n}{k} +
+ x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}} + + x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}} +
+ \left(\frac{\frac{\frac{1}{2}}{\frac{3}{4}}}{\frac{\frac{5}{6}}{\frac{7}{8}}}\right) + + \left(\frac{\frac{\frac{1}{2}}{\frac{3}{4}}}{\frac{\frac{5}{6}}{\frac{7}{8}}}\right) +
+ \left| + a + + + \left| + b + \right| + \right| + + \left| + a + + + \left| + b + \right| + \right| +
+ \sqrt{x}+\sqrt{\frac{x}{\frac{ + }{\frac{ + }{ + }}}}+\sqrt{\frac{x}{\frac{ + }{\frac{ + }{\frac{ + }{\frac{ + }{ + }}}}}} + + \sqrt{x}+\sqrt{\frac{x}{\frac{ + }{\frac{ + }{ + }}}}+\sqrt{\frac{x}{\frac{ + }{\frac{ + }{\frac{ + }{\frac{ + }{ + }}}}}} +
+ 1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness} + + 1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}^M +
+ x\ + \ + \ + +\ + \ + \ + y + + x\ + \ + \ + +\ + \ + \ + y^M +
+ \sum + _{n=0}^3\cos + x + + \sum + _{n=0}^3\cos + x^M +
+ \vec + x + + + \tilde + x + + + \vec + A + + + \tilde + A + + + \vec{abcd} + + + \tilde{abcd} + + \vec + x + + + \tilde + x + + + \vec + A + + + \tilde + A + + + \vec{abcd} + + + \tilde{abcd}^M +
+ \int + _{\phi + =0}^{2\pi + }\int + _{\theta + =0}^{\pi + }\int + _{r=0}^{\infty + }f(r,\theta + ,\phi + )r^2\sin + \theta + drd\theta + d\phi + + + \int + _{\phi + =0}^{2\pi + }\int + _{\theta + =0}^{\pi + }\int + _{r=0}^{\infty + }f(r,\theta + ,\phi + )r^2\sin + \theta + drd\theta + d\phi + +
+ \int_0^{\frac{\frac{1}{2}}{3}} + \int_0^{\frac{1}{\frac{2}{3}}} + \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}} + + \int_0^{\frac{\frac{1}{2}}{3}} + \int_0^{\frac{1}{\frac{2}{3}}} + \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}} +
+ \overline{abc} + + \overline{abc} +
+ \overleftarrow{abc} + + \overleftarrow{abc} +
+ \overrightarrow{abc} + + \overrightarrow{abc} +
+ \overleftrightarrow{abc} + + \overleftrightarrow{abc} +
+ \overarc{abc} + + \overarc{abc} +
+ + +
+ +

+ Parentheses vertical alignment at font sizes ranging from 10px to 24px: + +

+ + +

Dynamic rendering performance

+

+ LaTeX: + +

+

+ HTML: + +

+ +

+ There should be no space between here + and here, even in IE8 +

+ +

Textcolor

+ +

+ Colors should match their names: + + \textcolor{#0000FF}{blue} + \textcolor{red}{red} = \textcolor { + rgb(255, -10, 255) } {magenta} + +

+

+ Nested \textcolor: the 2 should be red, the "a+" green, the + 4 blue, and the "+b" green again. + + e^{\textcolor{#FF0000}{2}}\sin(x^\textcolor{#00FF00}{{a+\textcolor{blue}{4}+b}}) + +

+ +

Adding CSS class

+ +

+ Second term should be styled like this: + x+\class{testclass}{y}+z +

+ +

substituteTextarea

+ +

+ In Safari on iOS, this should be focusable but not bring up the + on-screen keyboard; to test, try focusing anything else and confirm this + blurs: (confirmed working on iOS 6.1.3) +

+ +

overrideKeystroke and overrideTypedText

+ +

+ Should be able to prevent typing in this field: + 1+2+3 +

+ +

+ Should wrap anything you type in '<>': + 1+2+3 +

+ +

Text mode

+ +

+ Spaces at the beginning and end of text mode blocks should be visible: + 1\text{ And }2 +

+ +

+ Mutiple consecutive spaces in the middle of a text mode block should not + collapse into one space: + \text{three spaces} +

+
    +
  • + [Firefox] Should not change '3' to '33' when you select all and press + right arrow. +
  • +
  • [All Browsers] ctrl-c or cmd-c should not throw an error
  • +
+ 3 + +

+ substituteKeyboardEvents (legacy, use overrideKeystroke and + overrideTypedText instead) +

+ +

+ Should be able to prevent cut, typing, and pasting in this field: + 1+2+3 +

+ +

+ Should wrap anything you type in '<>': + 1+2+3 +

+ +
    +
  • + [Firefox] Should not change '3' to '33' when you select all and press + right arrow. +
  • +
  • [All Browsers] ctrl-c or cmd-c should not throw an error
  • +
+ 3 +
+ + + + +